2012-09-21 61 views
3

这里是代码为什么子类中的静态块没有被执行?

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 

似乎在儿童类的静态块没有得到执行,但为什么?这是反直觉,不是吗?

补充:
,使之更加明确,我列出 分以下:

  1. 作为@axtavt说,根据JLS 12.4.1,儿童类加载,但不初始化。
  2. 但是@Alexei Kaigorodov指出,根据jvms-5.5, 类子应该初始化,因为执行 指令getstatic对Child类。

你觉得呢?

supplement2:
@Alexei Kaigorodov已经更新了主意,因此它似乎并无意见分歧离开了。但我认为阿列克谢凯戈罗多夫的观点是有启发性的,所以我把它留在那里。

谢谢大家。

+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加载,而不是它的初始化。

+2

所以,这意味着子类加载,但没有初始化,对不对? – turtledove

+0

@turtledove:是的。 – axtavt

+0

这是合理的,但很混乱! – turtledove

3

由于Child.name确实是Parent.name,孩子是不需要的。

您可能会觉得这很有趣。

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 
} 
+0

但是Child类被加载,我认为静态块应该在加载类时或执行时被执行? – turtledove

+0

Child类被加载,如果我删除ClassResolution $ Child.class文件并运行ClassResolution,我将得到ClassNotFoundException。 – turtledove

+1

孩子在字节代码中被引用,但大多数事情都是尽可能懒惰地完成的。在这种情况下,JVM不会初始化“Child”以访问其父类中的字段。 –

1

总之,Child.name等于Parent.name,并且编译器编译它像这样。

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

虽然您的代码指向Child.name,但它在内部为Parent.name,由编译器处理。然后,因为Child类未初始化,所以永不运行<clinit>静态初始化程序块,并且name保持为“Parent”。

2

JLS#12.4.1. When Initialization Occurs

静态字段(§8.3.1.1)仅引起类或接口,实际上声明它的初始化,即使它可能是通过一个子类的名称被称为参考,子接口或实现接口的类。

我上面猜说,这一切..

+1

这是因为编译器保留直接引用该字段的权利。但在我们的例子中,保留了对Child的引用,所以我们必须阅读JVM规范:http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5“Upon执行getstatic,putstatic或invokestatic指令时,声明已解析字段或方法的类或接口如果尚未初始化,则会初始化。“在代码中,我们看到'getstatic so/ClassResolution $ Child.name'指令。 –

0

哎呀,我错了,参照子未删除和类真的加载,但没有初始化。恭喜,您发现了一个JVM错误。转到Oracle站点并将其归档。如果你不想这么做,请告诉我,我会自己做。

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

+0

as axtavt指出,JLS在一个Class被初始化的时候指定了,我认为它在被加载的时候也被初始化了。所以看起来这不是一个错误,而是一个陷阱! – turtledove

+0

你发现什么是一个错误?一个类没有初始化加载的事实?那肯定不是一个bug,一个类可以随时加载。即使没有引用类的字段,也会产生错误这一事实?在加载类之前,运行时不会知道它是否拥有引用的字段,所以再次没有错误。 –

+0

@ Marko Topolnik查看我对Amid的回答的评论 –