昨日,某群友在某群里发了一个问题,内容如下:
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(Sub.this.baseName); } } public static void main(String[] args){ Base b = new Sub(); } }以上Base类的main()方法,到底输出内容是什么?
有的同学猜base,理由为构造Sub时会调用super的构造方法,而Base的构造方法会调用其callName()方法,所以输出的是父类的baseName,即内容为base,而有的同学猜是sub,理由是构造Sub时虽然会调用super的构造方法,而Base的构造方法会调用其callName()方法,但是子类Sub重写了父类Base的callName()方法,所以输出的应该是子类的baseName,即内容为sub。
而我的分析,却应该是null,为什么呢?把这个类稍微改造如下:
public class Base { static { System.out.println("父类Base的静态代码块被调用了!"); } private String baseName = "base"; public Base() { System.out.println("父类Base的构造方法被调用了!"); System.out.println("父类Base的成员变量baseName被初始化为" + baseName); callName(); } public void callName() { System.out.println("父类Base的callName()方法被调用了!"); System.out.println(baseName); } static class Sub extends Base { static{ System.out.println("子类Sub的静态代码块被调用了!"); } public Sub(){ System.out.println("子类Sub的构造方法被调用了!"); } private String baseName = "sub"; public void callName() { System.out.println("子类Sub的callName()方法被调用了!"); System.out.println(baseName); } } public static void main(String[] args) { Base b = new Sub(); } }执行下Base的main()方法,结果会是什么呢?答案如下:
父类Base的静态代码块被调用了! 子类Sub的静态代码块被调用了! 父类Base的构造方法被调用了! 父类Base的成员变量baseName被初始化为base 子类Sub的callName()方法被调用了! null 子类Sub的构造方法被调用了!为什么会是这个样子呢?我们简单分析它的大体流程应该如下:
1、首先,执行Base的main()方法中的Base b = new Sub();语句时,遇到Sub类,JVM会先加载Sub类,但是加载它时遇到了extends关键字,所以会在加载Sub类前先加载其父类Base,而第一行输出的“父类Base的静态代码块被调用了!”正好验证了这一点;
2、其次,该轮到加载Sub类了,故第二行输出为“子类Sub的静态代码块被调用了!”;
3、然后,类加载完了,该执行new操作了,众所周知,new操作是实例化一个对象,那么,子类被实例化时,是不是会调用父类的构造方法呢?答案是肯定的,而第三行的输出结果也确实是“父类Base的构造方法被调用了!”;
4、在父类Base的构造方法中,我们输出了父类Base的成员变量baseName的值,输出的值也确实是base;
5、接下来,该调用callName()方法了,那么这个callName()是应该调用父类的还是子类的呢?由于是初始化子类,而子类也恰好重写了父类的callName()方法,那么结果应该肯定是调用子类的callName()方法了,而第五行输出结果也确实是“子类Sub的callName()方法被调用了!”;
6、既然调用子类的callName()方法,那么就应该输出子类的baseName的值了,是不是就应该是sub呢?先卖个关子,再看下一步输出;
7、第七行输出结果为“子类Sub的构造方法被调用了!”,说明了什么?在调用子类callName()方法时,子类Sub还没有被实例化,那么它成员变量baseName的值就不应该是“sub”,而只能是null了,所以回过头来,第6行结果输出null也就不足为奇了!
特别需要说的,如果我们把子类的成员变量baseName定义为static,那么结果就不一样了,它会输出为sub,因为虽然在Sub构造方法执行前就调用子类的callName()方法,但是由于子类成员变量baseName被定义为static,在子类被JVM加载时,baseName的初始化就会被执行了,输出结果如下:
父类Base的静态代码块被调用了! 子类Sub的静态代码块被调用了! 父类Base的构造方法被调用了! 父类Base的成员变量baseName被初始化为base 子类Sub的callName()方法被调用了! sub 子类Sub的构造方法被调用了!
以上就是针对那位群友问题的全部解答。
下面,简要回顾下Java静态内部类、普通内部类等的区别:
1、构造形式:静态内部类有两种构造方式,第一种是直接new 内部类名(),比如上面的Base b = new Sub(),而第二种则是根据外部类的类名.内部类名()进行new操作,比如Base b = new Base.Sub();普通内部类则只能先显示构造外部类实例,再通过外部类实例.new 内部类名()来构造内部类实例,比如Base b = new Base().new Sub();
2、静态内部类可以有静态成员,而非静态内部类则不能有静态成员;
3、静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;
4、非静态内部类的非静态成员可以访问外部类的非静态变量。
未完待续!