本文要讲的是继承问题,是java基础部分,但又是面向对象的核心知识。许多初学者在理解继承和多态的时候容易记忆混淆,知识点不清晰,本文将会在一定程度上梳理继承的问题。
为什么说:继承引发了一场血案?原因是博主本来只是想写一个继承的小调试博客,但是发现要想把继承讲清楚,就不得不深入到JVM中去,而JVM内容繁多,要从JVM中讲述清楚继承,不是一篇博客就可以完成的。为了能够完成这个伟大目标,博主重新梳理JVM知识,再翻阅各种书籍查阅各方言说,进行汇总、提炼、形象化展示,期望能呈现出一个精彩的JVM世界来。其过程可谓是呕心沥血、鞠躬尽瘁、废寝忘食、闻鸡起舞、夜以继日、唧唧复唧唧……实则是一场无声的血案。
经上述吐槽后,我们先来看看浅谈虚拟机系列第一篇,血案的开始。
首先来看三段代码。
父类:
public class Father{private String name = "My name is David Beckham"; //大卫贝克汉姆 public Father(){ System.out.println(this.name); } }
子类:
public class Kid extends Father{ private String name = "My name is Brooklyn Joseph Beckham"; //贝克汉姆大儿子布鲁克林 public Kid(){ System.out.println(this.name); } }
主线程:
public class Test{ public static void main(String[] args){ Kid k= new Kid(); return; } }
输出结果:
My name is David Beckham My name is Brooklyn Joseph Beckham
以上代码只是简单地展示了类实例化的顺序,先实例化父类,再实例化子类。默认调用为无参构造函数。那么父类的实例化具体是在子类中哪一行开始的呢?通过以下代码来看
父类:
public class Father{ private String name = "My name is David Beckham"; //大卫贝克汉姆 public Father(){ System.out.println(this.name); } public Father(String s){ //新增加的含参构造函数 System.out.println(this.name + s); } }
子类:
public class Kid extends Father{ private String name = "My name is Brooklyn Joseph Beckham"; //贝克汉姆大儿子布鲁克林 public Kid(String s){ //传入字符串数组" love football!" System.out.println(this.name + s); } }
主线程中把Kid kid = new Kid();改为Kid kid = new Kid(" love football");
输出结果:
My name is David Beckham My name is Brooklyn Joseph Beckham loves football!
从这个case可以看到,子类依旧调用父类的无参构造函数。而调用位置具体是在子类构造函数被调用之前还是之后?为了解答这个问题,将在子类中显示调用父类构造函数。在子类构造函数第一行加上super(s),如下:
public Kid(String s){ super(s); System.out.println(this.name + s); }
输出结果:
My name is David Beckham loves football! My name is Brooklyn Joseph Beckham loves football!
从以上可以看出。类的实例化将先实例化父类,而父类构造函数被调用的地方在子类被调用构造函数的第一行。默认调用父类的无参构造函数。当父类没有无参构造函数而又没有显示给出正确的父类构造函数调用的话,会出现如下报错:
未定义隐式超构造函数 Father()。必须显式调用另一个构造函数
在此提及下关于向上向下转型的理解,很多developer对此的理解方式是硬背,但是总会混淆。从这个地方来理解,由于父类在子类的构造函数第一行被实例化,因此一个声明为父类的引用指向子类,可以指向子类中的实例化父类对象从而得到初始化,而不会出现空指针的Exception违例。
反过来,子类的引用指向父类,由于父类没有初始化子类,因此会出现空指针的报错。