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

时间:2012-09-21 11:52:53

标签: java static-block

这是代码

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. 但根据jvms-5.5,@ Alexei Kaigorodov指出, class Child应该初始化,因为执行了 关于Child class的指令getstatic。
  3. 您的想法是什么?

    supplement2:
    @Alexei Kaigorodov重新振作起来,所以似乎没有任何分歧。但我认为阿列克谢·凯戈罗多夫的观点很有启发性,所以我把它留在了那里。

    谢谢大家。

5 个答案:

答案 0 :(得分:4)

来自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); 

但请注意,静态字段不会被继承,因此Child.nameParent.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站点并将其归档。如果您不想这样做,请告诉我,我会自己做。

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