阅读 38

[Java]重学Java-继承

复用

随着码代码的时间增长,程序员会越发需要"复用"这个能力,比如最简单的对String类进行判空:

 str == null || "".equals(str);
复制代码

我们需要每次都这样写么?在面向对象中,我们可以利用类来封装这块逻辑进行复用。
现在,我们来思考:
除了方法,能否基于类的层面进行复用呢,当许多的对象都拥有相同的状态,我们是否可以用一个基类来对这些属性进行抽象?
答案是可以的,Java可以利用“继承”(Inheritance)的方式对类进行复用,只需要一个extends即可获取父类的能力(包括方法与属性)。

使用继承

UML

UML

编写Employee的子类Manager

  • com.tea.modules.model.base.Employee
package com.tea.modules.model.base;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

/**
 * com.tea.modules.model.base <br>
 * 雇员类
 *
 * @author jaymin
 * @since 2021/5/25
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
    /**
     * 名称
     */
    private String name;
    /**
     * 工资
     */
    private double salary;
    /**
     * 入职日期
     */
    private LocalDate hireDay;

    /**
     * 涨工资
     * @param byPercent 涨薪幅度,例如: 1.25
     */
    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary *= raise;
    }
}
复制代码

这是一个基类-Employee,有三个成员属性: name,salary,hireDay,提供了一个涨工资的方法: raiseSalary.

  • com.tea.modules.model.po.Manager

现在我们需要创建一个经理类用来直接继承Employee:
经理类,继承自雇员。经理属于特殊的雇员:
除了领工资外,经理还可以达到绩效后领取奖金

package com.tea.modules.model.po;

import com.tea.modules.model.base.Employee;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.time.LocalDate;

/**
 * com.tea.modules.model.po <br>
 * 经理类,继承自雇员。经理属于特殊的雇员:<br>
 * 除了领工资外,经理还可以达到绩效后领取奖金 <br>
 * Employee->超类(super class)|基类(base class)|父类(parent class)<br>
 * Manager->子类(sub class)|派生类(derived class)|孩子类(child class) <br>
 *
 * @author jaymin
 * @since 2021/5/25
 */
@Data
@NoArgsConstructor
@ToString(callSuper = true)
public class Manager extends Employee {
    /**
     * 奖金
     */
    private double bonus;

    public Manager(String name, double salary, LocalDate hireDay, double bonus) {
        super(name, salary, hireDay);
        this.bonus = bonus;
    }

    /**
     * 经理的工资由[工资+奖金]组成
     *
     * @return double
     */
    @Override
    public double getSalary() {
        // 注意这里要使用super关键字,否则会无限递归
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }
}
复制代码

Employee->超类(super class)|基类(base class)|父类(parent class)
Manager->子类(sub class)|派生类(derived class)|孩子类(child class)

  • Demo
package com.tea.modules.java8.extend;

import com.tea.modules.model.base.Employee;
import com.tea.modules.model.po.Manager;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

/**
 * com.tea.modules.java8.extend <br>
 * 理解继承
 *
 * @author jaymin
 * @since 2021/5/25
 */
public class ManagerDemo {

    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        LocalDate hireDay = LocalDate.of(2021, 5, 25);
        Employee manager = new Manager("经理", 20000, hireDay, 10000);
        Employee jack = new Employee("Jack", 10000, hireDay);
        Employee james = new Employee("James", 10000, hireDay);
        employees.add(manager);
        employees.add(jack);
        employees.add(james);
        employees.forEach(System.out::println);
    }
}
复制代码
  • Result
Manager(super=Employee(name=经理, salary=30000.0, hireDay=2021-05-25), bonus=10000.0)
Employee(name=Jack, salary=10000.0, hireDay=2021-05-25)
Employee(name=James, salary=10000.0, hireDay=2021-05-25)
复制代码

多态

在最后一句employees.forEach(System.out::println);中:
对于Manager类型的manger引用,调用的是Manager类的toString(不仅打印了自己的信息,还打印了父类的信息).
对于Employee类型的jack引用,调用的是Employee类的toString.
这个过程由虚拟机来控制,可以用一个变量来表示多种实际类型的现象称为多态(polymorphism),在运行时可以自动选择调用哪个方法的现象叫做动态绑定(dynamic bingding).

  • 子类赋值给超类变量
 Employee manager = new Manager("经理", 20000, hireDay, 10000);
复制代码

反之则无效,无法用Manager去接收一个Employee变量.

单一继承原则

java只支持单一继承,不支持同时继承多个类。但是你可以多层级继承,如图所示:
单一继承

方法调用

方法调用

再聊final

被final修饰的变量不可变,同样,被final修饰的类或者方法也是不可以被扩展和重写的。某种意义上来说,被final修饰的方法就像被"锁"住了一样.

这个特性比较重要,在后面的Spring框架中,我们会了解到,被final修饰的方法使用CGLIB是无法做增强的.

类型转换

这里我们创建了List的列表,里面既存放了Employee类型的对象,也有Manager类型的对象.那么我们需要用到Manager的方法时,就需要用到类型强制转换了

public static void main(String[] args) {
    List<Employee> employees = new ArrayList<>();
    LocalDate hireDay = LocalDate.of(2021, 5, 25);
    Employee manager = new Manager("经理", 20000, hireDay, 10000);
    Employee jack = new Employee("Jack", 10000, hireDay);
    Employee james = new Employee("James", 10000, hireDay);
    employees.add(manager);
    employees.add(jack);
    employees.add(james);
    // 类型强制转换
    for (Employee employee : employees) {
        if(employee instanceof Manager){
            Manager managerPerson = (Manager) employee;
            System.out.println(managerPerson.getSalary());
        }
    }
}
复制代码

Object

所有的Java对象都继承自Object类,也就是说,你可以用Object接收任意类型的对象。在Object中,包含了8个方法,下面我们来简单了解一下:

  • equals

Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,比较的是两个对象的引用值是否一致。但通常情况,我们都需要重写equals,来比较两个对象的状态是否一致。

public boolean equals(Object obj) {
    return (this == obj);
}
复制代码
  • getClass

返回此 Object 的运行时类。返回的 Class 对象是被表示的类的静态同步方法锁定的对象

  • hashCode

返回对象的哈希码值。
关于hashCode有以下规定:
对于同一个对象,多次调用hashCode必须返回相同的整数.
equals方法比较相等,hashCode返回值必须相等.
equals方法比较不相等,hashCode可以相等也可以不等.

  • toString

toString返回对象值的字符串,在平时开发中,应该始终覆盖toString方法,否则只会打印引用信息,无法查看对象的状态。

数组toString推荐使用Arrays.toString(array);

剩下的方法,在后面的章节进行讲解.

加载具有继承关系的对象

父类的static->父类构造器->子类的static->子类的构造器.

文章分类
后端
文章标签