5 回答
从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);
但是请注意,静态字段不是继承的,因此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."<init>":()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
}
简而言之,Child.name
等于Parent.name
,编译器就是这样编译的。
因为name
是Parent类的静态字段,所以是Parent中的类方法,不是Child。Java 编译器允许一个快捷方式,其中子类可以调用静态父类方法/字段,就好像它们来自自己的类一样,但在内部它们是相对于父类进行编译的。
尽管您的代码引用了Child.name
,但它是内部Parent.name
的,由编译器处理。然后,因为 Child 类没有被初始化,所以<clinit>
静态初始化块永远不会运行,并且name
仍然是“Parent”。
糟糕,我错了,对 Child 的引用没有被删除,并且类确实被加载了,但没有初始化。恭喜,您发现了一个 JVM 错误。转到 Oracle 站点并归档。如果您不想这样做,请告诉我,我会自己做。
编辑:我又错了,请参阅下面的评论。这不是一个错误,这是一个“陷阱”。