3
4

5 回答 5

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,但不会触发其初始化。

于 2012-09-21T12:06:28.697 回答
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
}
于 2012-09-21T11:53:59.950 回答
2

JLS#12.4.1。初始化发生时

对静态字段的引用(第 8.3.1.1 节)只会初始化实际声明它的类或接口,即使它可能通过子类、子接口或实现接口的类的名称来引用。

我想上面已经说明了一切..

于 2012-09-21T12:07:05.047 回答
1

简而言之,Child.name等于Parent.name,编译器就是这样编译的。

因为name是Parent类的静态字段,所以是Parent中的类方法,不是Child。Java 编译器允许一个快捷方式,其中子类可以调用静态父类方法/字段,就好像它们来自自己的类一样,但在内部它们是相对于父类进行编译的。

尽管您的代码引用了Child.name,但它是内部Parent.name的,由编译器处理。然后,因为 Child 类没有被初始化,所以<clinit>静态初始化块永远不会运行,并且name仍然是“Parent”。

于 2012-09-21T11:58:29.910 回答
0

糟糕,我错了,对 Child 的引用没有被删除,并且类确实被加载了,但没有初始化。恭喜,您发现了一个 JVM 错误。转到 Oracle 站点并归档。如果您不想这样做,请告诉我,我会自己做。

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

于 2012-09-21T12:29:46.120 回答