这是无意间在网上看到的一道考面向对象的一道题,乍眼一看发现做不出来。好东西就要来分享一下,请看题。
public class Base { private String baseName = "base"; public Base() { callName(); } public void callName() { System.out.println(baseName); } static class Sub extends Base { private String baseName = "sub"; public void callName(){ System.out.println(baseName); } } public static void main(String[] args) { Base b = new Sub(); System.out.println(b); } } 复制代码
求程序最后的输出。
估摸着这道题考了多态,类的初始化以及成员加载的顺序。
先入为主,以上的代码中有一个main方法,作为一个类的入口,执行main方法触发了类加载的过程。
对于什么时候开始类的初始化,以下摘自深入理解java虚拟机第七章? 在类未被初始化过的前提下。
对于一个类的初始化阶段就是执行类构造器方法的过程。方法是由编译器自动收集类中的所有==类变量==的赋值动作和==静态语句块==中的语句合并产生的。
Java虚拟机会对做以下规定。
初始化后,接着就开始执行类中的方法。在执行new Sub(),它会触发对象的初始化。在java中有多种创建对象的方式,new是最直观最常用的那种。其他还有
在对象被创建时,虚拟机会为其分配空间来存放对象本身的实例变量还有从父类继承过来的变量,在这个过程中还会为各个变量设置初始默认值。 接着就会进行对象的初始化。一般分为3个步骤,跟类初始化其实差不太多。
对于1、2并没有严格意义上的执行顺序,谁在前面谁先跑。但是3一定是在1、2之后。为什么?构造方法的目的就是为了在创建对象时给成员变量赋初始值,如果成员都没定义,那构造函数就没有意义了。但是对于1、2点来说,谁在上面谁就先执行。
在new Sub()触发对象的初始化后,会先调用Sub类的无参构造方法,因为Sub类中没有显式声明一个无参的构造方法,那就调用它默认的无参构造。子类的构造方法中都有一个super()去调用父类的无参构造。
在Java中有以下这么几个规则。
至于以上3条规则的原因我还没找到官方的解释,找到再补上。
看到这里,或许可以瞄一眼另一道题
class Person { String name = "No name"; public Person(String nm) { name = nm; } } class Employee extends Person { String empID = "0000"; public Employee(String id) { empID = id; } } public class Test { public static void main(String args[]) { Employee e = new Employee("123"); System.out.println(e.empID); } } 复制代码
这题考的是继承关系中,父类子类构造器初始化的问题。这边再拷一下上面的一句话 -> 子类的构造方法中都有一个super()去调用父类的无参构造。再瞄一眼代码,WTF 爸爸的无参构造去哪了?如果父类没有无参的构造函数的情况,子类需要在自己的构造函数中显式调用父类的构造函数。
根据上面的规则,代码就变成了
public Sub() { // super(); 隐式的调用父类的无参构造 callName(); // -> System.out.println(baseName); private String baseName = "sub"; } 复制代码
所以结果一目了然了吧,在执行callName()的时候baseName还未初始化,执行到这一步的时候要搞清楚成员变量的初始化顺序。
根据以上一波文档的寻找发现构造器初始化顺序大概就是 父类静态 -> 子类静态 -> 父类成员 -> 父类构造 -> 子类成员 -> 子类构造
最后给一个网上找到的一检测题来练一波手。wota:Java初始化顺序
以后碰上这类面试题往上套就是了。通过理解这个面试题,目的是更深入的了解了类初始化和对象初始化的过程。
学习的最终目的并不是为了面试,面试只是一个激励学习的动机。把握面试题,享受学习新知识的乐趣。