《Java核心技术》 | 继承

一、类、超类和子类

1.1 多态

已经存在的类称为超类基类或者父类;新类称为子类派生类或者孩子类。通常,字类拥有比父类更多的功能。

在字类中可以增加域、增加方法或者覆盖超类的方法,但是绝对不能删除继承的任何域和方法

可以使用 super 方法实现对超类构造器的调用,其中 super 调用构造器的语句必须是字类构造器的第一条语句

public class Employee {

    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    ...
}

public class Manager extends Employee {

    private double bonus;

    public Manager(String name, int age, double salary){
        //语句1
        super(name, age, salary);
        bonus = 10.00;
    }

}

如果字类没有显示的调用超类的构造器,将自动调用超累默认构造器(没有参数)。如果超类没有定义不带参数的构造器,但是定义了带参数的构造器,此时如果在子类构造器中没有显示的调用父类的带参数的构造器,即没有写 1 这条语句,则会产生错误

一个变量可以指示多种实际类型的现象称为多态。在运行时候能够自动选择调用哪个方法的现象称为动态绑定

这里有一个问题,如果我们使用以下的语句

//语句1
Manager[] managers = new Manager[10];
//语句2
Employee[] staff = managers;
//语句3
staff[0] = new Employee("123",12,12,34,23)

还是上面的继承关系,这里我们将子类(Manager 类)的引用转换成父类(Employee 类)的引用,前两句没有问题,且不用强制转换,想一下,如果一个人是经理,那么他也一定是雇员,雇员才是前提。此时相当于雇员(Employee) 里面存的都是经理(Manager) 的信息

但是第3句,本来规定存储经理(Manager) 信息的数组里面又添加了一条雇员(Employee) 信息,这显然是不对的。好比,这个岗位只是招研究生的,结果你一个本科生去投简历,那人家肯定不要你,一个道理,范围都定死了。

此时会报以下错误

如果第三句改成

staff[0] = new Manager("123",12,12,34,23);

那肯定不会出错了,因为本来就是规定存的经理(Manager),现在 new 一个经理,那正好符合规则

1.2 动态绑定过程

1.首先 new 一个字类的对象,创建一个字类的引用指向他。这个时候,如果调用下面的 getSalary(xxx) 方法,则编译器便会列举子类中名为 getSalary 的方法和其超类中访问属性为 public 且名为 getSalary 的方法

Manager manager = new Manager();
manager.getSalary(90);

2.在字类和父类查找名为 getSalary 方法的过程中,只要参数匹配,就调用这个方法。期间,编译器会自动作参数的类型转换,即如果传入的参数是 int 类型,如果定义的相同名称的方法的参数类型比他大(double,long) 等等,也会自动调用该方法,因为会进行自动转换。而如果没能找到与参数类型匹配的方法,或者有多个方法与之对应,则会报错

public class Employee {
    /*public double getSalary(int a){
        System.out.println("a");
        return a;
    }*/
}

public class Manager extends Employee {
    //语句1
    public double getSalary(double a){
        System.out.println("sal double");
        return 1;
    }

    //语句2
    public double getSalary(int a) {
        System.out.println("sal int a");
        return 1;
    }
}

public class ManagerTest {
    public static void main(String[] args) {
        Manager manager = new Manager();
        manager.getSalary(90);
    }
}

结果:sal int a

如果把语句2去掉,同时父类不变,那么

输出:sal double

3.如果是 private、static、final 方法,那么编译器肯定知道该调用哪个方法,该调用方式称为静态绑定。与之对应的是,对象自己去找应该调用哪个方法,这种调用方式是动态绑定

4.由于每次都要方法去字类父类中寻找对应的方法,很麻烦,开销很大。因此,虚拟机为每个类创建了一个方法表,列出了所有方法,在真正需要调用方法的时候,虚拟机直接查找方法表就行了。

比如 Employee 的方法表

方法名 所在的类
getName Employee
getSalary() Employee
getHireDay Employee

这里只列举了一部分,另外还有默认调用 Object 的类,没有写出

Manager 的方法类(部分),其中第一个类是从 Employee 中继承可得,另一个是自己增加的

方法名 所在的类
getSalary() Employee
getSalary(int a) Manager
getSalary(double a) Manager

举例

比如 manager.getSalary(90) 这个方法。虚拟机直接在 Employee 和 Manager 类中搜索定义 getSalary 方法的类,由于 Manager 中就有两个相同签明的方法,于是就找参数为 int 的那个类就好了,此时虚拟机知道该调用哪个方法。

PS

覆盖一个方法的时候,字类方法不能低于父类方法的可见性。如果父类是 public 修饰的,由于字类不能比父类小,因此只能是 public。如果不写,则使 default,也会报错

如果父类是 protected,那么字类只能是 protected 或者 public,不写也会报错(不写则是 package-private)

class Employee {
    protected void aa(){}
}
class Manager {
    @Override
    void aa() {}    //报错

    protected void aa(){}  //正确

    public void aa(){}  //正确

}
1.3 fina 类及其方法

不允许进行扩展(继承)的类称为 final 类,当一个类被声明为 final 类型,那么其中的所有方法自动转为 final 方法,但是域不会转为 final

public final class A {

    void printA(){}

}

当使用以下语句时

被 final 修饰的方法不能被覆盖

public class A {

    final void printA(){}

}

1.4 强制类型转换

从上到下进行类型转换,即从大的范围转为小的范围

在程序设计中,尽量少的使用类型转换和 instanceof 运算符

1.5 抽象类

抽象类既可以声明一个没有实现的方法,也可以创建一个实现了的方法。字类继承了父类的抽象类之后,会对父类声明的方法加以实现

public abstract class Person {

    private String name;

    public Person(String name) {
        this.name = name;
    }

    public abstract String getDescription();

    public String getName() {
        return name;
    }

}

public class Employee extends Person{

    public Employee(String name) {
        super(name);
    }

    @Override
    public String getDescription() {
        return null;
    }
}

一个抽象类,即使不含抽象方法,也可以将类声明为抽象类

抽象类不能被实例化

如果将一个类声明为 abstract,就不能创建这个类的对象。如果定义了一个抽象类的对象,那么他是能通过一个非抽象字类来引用。如上面那个例子,最终可以通过以下方法来创建抽象类的实例,然后调用抽象类的方法

Person person = new Employee("aaa");
person.getName();


1.6 各个修饰符的范围
  1. private:仅对本类可见
  2. public:对所有类可见
  3. protected:对本包和所有字类可见
  4. default:对本包可见

二、装箱和拆箱

Integer a = 100;    //装箱
int b = a;  //拆箱

装箱调用 Integer.valueOf 方法,拆箱调用 intValue 方法

三、参考

《Java核心技术 卷1》

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/82118898