为什么在子类上调用在其超类中声明的静态方法时,不会调用子类的静态初始化器?

时间:2014-09-19 20:14:38

标签: java inheritance static

鉴于以下类别:

public abstract class Super {
    protected static Object staticVar;

    protected static void staticMethod() {
        System.out.println( staticVar );
    }
}

public class Sub extends Super {
    static {
        staticVar = new Object();
    }

    // Declaring a method with the same signature here, 
    // thus hiding Super.staticMethod(), avoids staticVar being null
    /*
    public static void staticMethod() {
        Super.staticMethod();
    }
    */
}

public class UserClass {
    public static void main( String[] args ) {
        new UserClass().method();
    }

    void method() {
        Sub.staticMethod(); // prints "null"
    }
}

我没有针对像&#34这样的答案;因为它在JLS中就像这样指定了。"。我知道它是,因为JLS, 12.4.1 When Initialization Occurs只读:

  

类或接口类型T将在第一次出现以下任何一个之前立即初始化:

     
      
  • ...

  •   
  • T是一个类,调用T声明的静态方法。

  •   
  • ...

  •   

我对是否有一个很好的理由感兴趣,例如:

  
      
  • T是S的子类,并且在T上调用由S声明的静态方法。
  •   

6 个答案:

答案 0 :(得分:10)

小心标题,静态字段和方法 NOT 继承。这意味着,当您在staticMethod()中对Sub发表评论时,Sub.staticMethod()实际上会调用Super.staticMethod(),然后Sub不会执行静态初始值设定项。

然而,问题比我第一眼看到的更有趣:在我看来,这不应该在没有警告的情况下进行编译,就像在类的实例上调用静态方法时一样。

编辑:正如@GeroldBroser指出的那样,这个答案的第一个陈述是错误的。静态方法也是继承的,但从不覆盖,只是隐藏。我将答案留给了历史。

答案 1 :(得分:3)

JLS特别允许JVM避免加载Sub类,它位于问题中引用的部分:

  

对静态字段的引用(第8.3.1.1节)仅导致实际声明它的类或接口的初始化,即使它可能通过子类的名称,子接口或实现的类来引用一个界面。

原因是避免不必要地使用JVM加载类。初始化静态变量不是问题,因为它们无论如何都没有被引用。

答案 2 :(得分:2)

原因很简单:JVM不要过早地做额外的工作(Java本质上是懒惰的)。

无论是编写Super.staticMethod()还是Sub.staticMethod(),都会调用相同的实现。而且这个父实现的实现通常不依赖于子类。 Super的静态方法不应该访问Sub的成员,那么初始化Sub的重点是什么呢?

你的例子似乎是人为的,没有精心设计。

使子类重写超类的静态字段听起来不是一个好主意。在这种情况下,Super的方法的结果将取决于首先触及哪个类。这也很难让Super的多个孩子有自己的行为。简而言之,静态成员不是为了多态 - 这就是OOP原则所说的。

答案 3 :(得分:1)

我认为这与jvm规范的this part有关:

  

每个帧(第2.6节)包含对运行时常量池(第2.5.5节)的引用,用于支持方法代码的动态链接的当前方法的类型。方法的类文件代码是指要调用的方法和要通过符号引用访问的变量。动态链接将这些符号方法引用转换为具体的方法引用,根据需要加载类以解析尚未定义的符号,并将变量访问转换为与这些变量的运行时位置相关联的存储结构中的适当偏移。

     

方法和变量的这种后期绑定使得方法使用的其他类中的更改不太可能破坏此代码。

在jvm规范的chapter 5中,他们还提到: 其中,可以初始化类或接口C,作为以下结果:

  

执行任何一个引用C(§new,§getstatic,§putstatic,§invokestatic)的Java虚拟机指令new,getstatic,putstatic或invokestatic。这些说明通过字段引用或方法引用直接或间接引用类或接口

     

...

     

执行getstatic,putstatic或invokestatic指令后,声明已解析字段或方法的类或接口已初始化(如果尚未初始化)。

在我看来,第一篇文档指出,任何符号引用都可以简单地解析和调用,而不考虑它来自何处。这个documentation about method resolution有以下说法:

  

[M] ethod解析试图在C及其超类中找到引用的方法:

     

如果C只使用方法引用指定的名称声明一个方法,并且声明是签名多态方法(第2.9节),则方法查找成功。描述符中提到的所有类名都已解析(第5.4.3.1节)。

     

已解析的方法是签名多态方法声明。 C不必声明具有方法引用指定的描述符的方法。

     

否则,如果C声明一个方法引用指定名称和描述符的方法,则方法查找成功。

     

否则,如果C有一个超类,则在C的直接超类上递归调用方法解析的第2步。

因此,从子类调用它的事实似乎只是被忽略了。为什么这样?在您提供的文档中,他们说:

  

意图是类或接口类型具有一组初始化器,使其处于一致状态,并且该状态是其他类观察到的第一个状态。

在您的示例中,当静态初始化Sub时,您可以更改Super的状态。如果在调用Sub.staticMethod时发生了初始化,那么jvm认为相同的方法会有不同的行为。这可能是他们谈论避免的不一致。

此外,这里有一些执行staticMethod的反编译类文件代码,显示了invokestatic的使用:

Constant pool:
    ...
    #2 = Methodref          #18.#19        // Sub.staticMethod:()V

... 

Code:
  stack=0, locals=1, args_size=1
     0: invokestatic  #2                  // Method Sub.staticMethod:()V
     3: return

答案 4 :(得分:0)

由于某种原因,jvm认为静态块不好,而且没有执行

我相信,这是因为你没有为子类使用任何方法,所以jvm认为没有理由" init"类本身,方法调用在编译时静态绑定到父级 - 静态方法的后期绑定

http://ideone.com/pUyVj4

static {
    System.out.println("init");
    staticVar = new Object();
}

添加其他方法,并在子

之前调用它
Sub.someOtherMethod();
new UsersClass().method();

或明确Class.forName("Sub");

Class.forName("Sub");
new UsersClass().method();

答案 5 :(得分:0)

根据这个article,当你调用静态方法或使用类的静态字段时,只会初始化该类。

以下是屏幕截图示例。enter image description here