为什么子类中的静态块不会被执行?
这是代码
public class ClassResolution { static class Parent { public static String name; static { System.out.println("this is Parent"); name = "Parent"; } } static class Child extends Parent { static { System.out.println("this is Child"); name = "Child"; } } public static void main(String[] args) throws ClassNotFoundException { System.out.println(Child.name); }}
我期望的是:
this is Parent this is Child Child
但实际上是:
this is Parent Parent
似乎Child类中的静态块没有被执行,但为什么呢? 这是反直觉,不是吗?
补充:
为了使它更清楚,我列出了 2 1分以下:
- 正如@axtavt所说,根据JLS 12.4.1 ,类Child已加载,但尚未初始化。
但@Alexei Kaigorodov指出,根据jvms-5.5 ,类Child应该初始化,因为在Child类上执行了getstatic指令。
你怎么看?
supplement2:
@Alexei Kaigorodov重新思考,所以似乎没有任何分歧。 但我认为阿列克谢·凯戈罗多夫的观点很有启发性,所以我把它留在了那里。
谢谢大家。
来自JLS 12.4.1 :
类或接口类型T将在第一次出现以下任何一个之前立即初始化:
- T是一个类,并且创建了T的实例。
- T是一个类,调用T声明的静态方法。
- 分配由T声明的静态字段。
- 使用由T声明的静态字段,该字段不是常量变量(第4.12.4节)。
- T是顶级类,并且执行在词典内嵌套在T中的断言语句(第14.10节)。
正如您所看到的,在您的代码中没有发生这些情况(请注意, name
在Parent
声明,而不在Child
),因此Child
不会被初始化并且不会执行其静态块。
如果您执行某些操作来触发Child
初始化,您将获得预期的输出:
new Child(); System.out.println(Child.name);
但请注意,静态字段不会被inheritance,因此Child.name
和Parent.name
实际上引用相同的字段。 这就是为什么在实践中使用类似于你的例子的代码没有多大意义。
另请注意,尽管Child.name
实际上引用了Parent.name
,但它仍然在字节码中被引用为Child.name
,因此您的代码会触发Child
加载,但不会初始化它。
由于Child.name
是Parent.name
,因此不需要Child。
你可能会发现这很有趣。
public class ClassResolution { static class Parent { public static String name; static { System.out.println("this is Parent"); name = "Parent"; } } static class Child extends Parent { static { System.out.println("this is Child"); name = "Child"; } static String word ="hello"; } public static void main(String[] args) { System.out.println(Child.name); System.out.println(Child.word); System.out.println(Child.name); } }
版画
this is Parent Parent this is Child hello Child
此类的javap
打印出保留对Child
的引用。
C:\>javap -c -classpath . ClassResolution Compiled from "ClassResolution.java" public class ClassResolution { public ClassResolution(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #3 // Field ClassResolution$Child.name:Ljava/lang/String; 6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 12: getstatic #5 // Field ClassResolution$Child.word:Ljava/lang/String; 15: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 21: getstatic #3 // Field ClassResolution$Child.name:Ljava/lang/String; 24: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return }
JLS#12.4.1。 发生初始化时
对静态字段的引用(第8.3.1.1节)仅导致实际声明它的类或接口的初始化,即使它可能通过子类的名称,子接口或实现接口的类来引用。
我想上面说的都是..
简而言之, Child.name
等于Parent.name
,编译器就像这样编译它。
因为name
是Parent类的静态字段,所以它是Parent中的类方法,而不是Child。 Java编译器允许一个快捷方式,其中子类可以调用静态父类方法/字段,就像它们来自它们自己的类一样,但在内部它们是根据父类编译的。
虽然您的代码引用了Child.name
,但它在内部是Parent.name
,由编译器处理。 然后,因为未初始化Child类,所以
静态初始化程序块永远不会运行,并且name
仍为“Parent”。
糟糕,我错了,没有删除对Child的引用,并且该类实际上已加载,但尚未初始化。 恭喜,您发现了一个JVM错误。 转到Oracle站点并将其归档。 如果您不想这样做,请告诉我,我会自己做。
编辑:我错了,请看下面的评论。 这不是一个错误,这是一个“陷阱”。