这是代码
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 点:
您的想法是什么?
supplement2:
@Alexei Kaigorodov重新振作起来,所以似乎没有任何分歧。但我认为阿列克谢·凯戈罗多夫的观点很有启发性,所以我把它留在了那里。
谢谢大家。
答案 0 :(得分:4)
来自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
的加载,但是不是它的初始化。
答案 1 :(得分:3)
由于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
}
答案 2 :(得分:2)
JLS#12.4.1. When Initialization Occurs
对静态字段的引用(第8.3.1.1节)仅导致实际声明它的类或接口的初始化,即使它可能通过子类的名称,子接口或实现的类来引用一个界面。
我猜以上就是这么说..
答案 3 :(得分:1)
简而言之,Child.name
等于Parent.name
,编译器会像这样编译它。
因为name
是Parent类的静态字段,所以它是Parent中的类方法,而不是Child。 Java编译器允许一个快捷方式,其中子类可以调用静态父类方法/字段,就像它们来自它们自己的类一样,但在内部它们是根据父类编译的。
虽然您的代码引用Child.name
,但它在内部Parent.name
,由编译器处理。然后,因为未初始化Child类,所以<clinit>
静态初始化程序块永远不会运行,name
仍为“父”。
答案 4 :(得分:0)
哎呀,我错了,没有删除对Child的引用,并且该类实际上已加载,但未初始化。恭喜,您发现了一个JVM错误。转到Oracle站点并将其归档。如果您不想这样做,请告诉我,我会自己做。
编辑:我错了,请看下面的评论。这不是一个错误,这是一个“陷阱”。