今天做了某公司的面试题,发现一题很有意思,写来记录。
源代码如下:
class Father {
public static final String F = "aa";
public Father() {
init();
}
public void init() {
System.out.println("Father init");
}
static {
System.out.println("init");
}
static {
System.out.println(Son.F);
}
}
class Son extends Father {
public static String F = "Son";
public void init() {
System.out.println("Son init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Father.F);
Father f = new Son();
System.out.println(f.F);
}
}
运行结果如下:
让我们来分析下流程:
①.当主函数中 System.out.println(Father.F); 这行代码执行时,因为Father类中的F是static final 修饰的并且是_显式的赋值了_(后面讲介绍没有显式的赋值)的常量(编译时常量),所以调用打印Father.F 只会打印该常量,并不会涉及到Father类的加载(把Father加载到内存),因此Father中的静态代码块不会被调用。
②.当执行Father f = new Son();这行代码时,因为要new Son();所以要加载Son类进内存,以为Son类还继承了Father类,要先把Father类加载到内存,Father内加载到内存后静态第一个静态代码快执行了,输出 init,当执行到第二静态代码块时,Son类还没有被加载进内存,因此打印Son.F输出为 null ,然后是Son类被加载到内存,调用 Son的默认构造器,然后再向上调用父类的默认构造器,别忘了此时可是this往上层级调用的,所以执行inin()方法,是Son中的 init()方法,输出 Son Init。
③.当执行System.out.println(f.F);该行代码时。由于Son类已经报给加载到内存了,代码块不会被执行,只会打印出 Son 类中 F 的值 aa。
至此程序结束。
让我们来修改下代码,只修改一行:
public static String F = "aa";
运行结果如下:
让我们来分析下流程:
①.当主函数中 System.out.println(Father.F); 这行代码执行时,此时 Father 类被加载到内存,然后执行静态代码块,执行第一个静态代码块,输出 init ,执行第二个静态代码块时,Son 类被加载进内存,然后输出 Son 类中的 F 为Son ,然后输出 Father 的 F 的值 aa。
②.当执行Father f = new Son();这行代码时,此时 Father 类 与 Son 类都已经别加载到内存,然后执行 Son 的默认构造器,再向上调运父类 Father 的默认构造器,执行 init() 方法(注意此时的this),输出 Son init。
③.当执行System.out.println(f.F);该行代码时。由于Son类已经报给加载到内存了,代码块不会被执行,只会打印出 Son 类中 F 的值 aa。
至此程序结束。
让我们来修改下代码,只修改一行:
public static final String F =String.valueOf(new Random().nextInt(10));
运行结果如下:
让我们来分析下流程:
执行结果与上边第一次修改代码时的结果一样(执行顺序一样,相对应的步骤的值被修改的代码替换掉了),这是为什么呢?还记得没有修改的原生面试题吗?我们在执行的第一步中说,(编译时常量:就这样的:public static final String F = “aa”;)(运行时常量呢:这样的:public static final String F =String.valueOf(new Random().nextInt(10));)看到区别了吗?* 编译时常量,如果用类名去调用时不会加载所在类的,而运行时常量是要加载所在类的。只用static修饰的变量在用 类名调用时也是需要加载所在类的。
总结:
①.static final 修饰的常量如果是编译时常量(在编译阶段就有明确的值),通过类名点调用时不需要加载所在类的。
②.static final 修饰的常量如果不是编译时常量(在编译阶段就没有明确的值),通过类名点调用时是要加载所在类的。
③.只用static修饰的变量在用 类名调用时也是需要加载所在类的。
④.还有人可能还迷惑代码中关于多态的一些调用的,总结规则:成员变量,静态方法看左边;非静态方法,编译看左边,运行看右边。
(原创个人观点,目前就该题而言只能是通过输出结果和个人断点调试等表象来得出结论,而不能从类的加载和调用机制来说明问题,问题先留这,等有了更深的理解,再来补充 ,欢迎各位给出建议和讨论)