Java 继承的实质

201 阅读5分钟

1 问题引入

你知道下面这段代码的输出结果吗?

public class A {    //父类A
    private int d;
    String name;
    
    public JustTest(int d){
        this.d = d;
        name = "father";
    }

    public static void main(String[] args) {
        Son son = new Son("son", 1);
    }

}

class B extends A{  //子类B
    String name;
    public Son(String name, int d) {
        super(1);
        this.name = name;
        System.out.println(this.name + " " + super.name);
    }
}

那改一下呢?


public class A {    //父类A
    private int d;
    String name;
    
    public JustTest(int d){
        this.d = d;
        name = "father";
    }

    public static void main(String[] args) {
        Son son = new Son("son", 1);
    }

}

class B extends A{  //子类B
    //String name;
    public Son(String name, int d) {
        super(1);
        this.name = name;
        System.out.println(this.name + " " + super.name);
    }
}

结果分别是:

son father //第一段代码
son son    //第二段代码

如果你能够答对,那本文可以就此关闭了
如果你有些疑惑,那我们继续

2 java 继承的实质

java中继承的实质,简略版:

  1. 子类对象里面还塞着一个父类对象
  2. 属性的查找机制

java中继承的实质,详细版:

  1. 在创建子类对象的同时,会相应的创建出一个对应的父类对象,后者只能被前者访问到(通过super关键字)。
  2. 直接调用对象的属性时,系统会先从对象本身查找。如果对象本身没有此属性,就会在这个对象对应的父类对象中查找。如此循环,直到找到该属性(或者没找到,编译报错)

2.1 有时必须在子类构造函数中调用父类构造函数的原因

对于结论1,值得注意的是,在系统创建父类对象时默认使用的是父类的无参方法,这也是为什么一旦父类的无参方法被覆盖(即声明了有参方法但没有声明无参方法),子类就必须在所有构造方法中显式调用父类的构造方法(注意,是“所有”)

 public class A {
     private int age;
     String name;
     public A(int age){
         this.age = age;
         name = "father";
     }
 }
 ​
 class B extends A {
     // 这里,你什么也不干会报错
     // 你写了构造函数,但没有调用父类的构造函数,也会报错
     
     // 必须手动调用父类的构造函数才能够通过编译,例如:
     public B() {     
         super(1);
     }
     
     public B(int age) {
         super(age);
     }
 }

原因很明显,引文结论1中说了。在创建子类对象的同时,会相应的创建出一个对应的父类对象,这个过程是必须的。

2.2 关于为什么属性和方法能够继承

可能有小朋友会有疑问。既然在创建子类对象的同时会创建一个父类对象,那么为什么我能够直接通过子类对象访问该属性呢?

这就是结论2所解释的内容,参看如下代码:

 public class A {
     int age;
     String name;
 ​
     public A(int age){
         this.age = age;
         name = "father";
     }
 }
 ​
 class B extends A {
     public B(int age, String name) {
         super(age);
         this.name = name;
     }
 }
 ​
 //在类外调用
 class Main {
     public static void main(String[] args) {
         B b = new B(1, "son");
         System.out.println(b.age + " " + b.name);
     }
 }

当通过this.属性或者子类对象.属性调用属性时,都遵从结论2的查找方式,子类对象中找不到,就从父类对象中找。

不过在外界看来,如果不知道这种查找机制,就好像age和name本身就是对象b中的属性一样。

事实上,“继承”这一行为并没有给子类带来额外的属性和方法,属性和方法始终只有一份,存在父类对象中

2.3 关于“覆盖”的解释

我们都知道,在子类中声明和父类同名的属性和方法会覆盖父类中的属性和方法。(为了简便,下面就只说属性,方法是相似的) 然而实际上,这种说法是存在一定诱导性的,会让我们下意识地认为,被覆盖的属性“消失了”,但事实上被覆盖的属性活得好好的,只是访问优先级降低了。

别跑,很好理解的。结论2说了,找属性是先从对象本身开始找的,那么如果对象自身声明了一个同名属性,那么不就直接“找到了”吗,自然是不会再继续往上找了,身在父对象中的另一个同名属性自然就不会被访问到了。

但不会被直接访问到,不代表这个属性死了,我们仍可以通过super关键字来访问这个属性。

 public class A {
     int age;
     String name;
 ​
     public A(int age){
         this.age = age;
         name = "father";
     }
 }
 ​
 class B extends A {
     String name;   //属性覆盖
     
     public B(int age, String name) {
         super(age);
         this.name = name;
 ​
         System.out.println("this: " + this.name); 
         System.out.println("super: " + super.name);
     }
 }
 ​
 //测试
 class Main {
     public static void main(String[] args) {
         B b = new B(1, "son");
         System.out.println(b.age + " " + b.name);
     }
 }

结果是:

 this: son
 super: father

看,被覆盖的属性确实活得好好的。

2.4 关于private属性会不会被继承的问题

我想看完上面这些介绍后,会对这个问题有自己的见解。

因为上文多次提到,所谓继承,并不是对父类中属性和方法的复制黏贴,而是将父类整个打包塞进子类。那自然包括了private属性和方法。

但需要注意,子类对象属于父类对象“外面”,所以子类对象也不能够直接调用父类对象的private属性和方法

 public class A {
     private String name;
 ​
     public A(){
         name = "father";
     }
 }
 ​
 class B extends A {
     public B(String name) {
         this.name = name; // error: name has private access in A
     }
 }

想要访问,只能够迂回前进,借助父类中的非private方法访问,例如:

 public class A {
     private String name;
 ​
     public A(){
         name = "father";
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 }
 ​
 class B extends A {
     public B(String name) {
         setName(name);
     }
 }

3 对 问题引入 中问题的解释

长草颜文字.gif 还需要解释吗?