为什么子类中的静态块不会被执行?

这是代码

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分以下:

  1. 正如@axtavt所说,根据JLS 12.4.1 ,类Child已加载,但尚未初始化。
  2. 但@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节)。

正如您所看到的,在您的代码中没有发生这些情况(请注意, nameParent声明,而不在Child ),因此Child不会被初始化并且不会执行其静态块。

如果您执行某些操作来触发Child初始化,您将获得预期的输出:

 new Child(); System.out.println(Child.name); 

但请注意,静态字段不会被inheritance,因此Child.nameParent.name实际上引用相同的字段。 这就是为什么在实践中使用类似于你的例子的代码没有多大意义。

另请注意,尽管Child.name实际上引用了Parent.name ,但它仍然在字节码中被引用为Child.name ,因此您的代码会触发Child加载,但不会初始化它。

由于Child.nameParent.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站点并将其归档。 如果您不想这样做,请告诉我,我会自己做。

编辑:我错了,请看下面的评论。 这不是一个错误,这是一个“陷阱”。