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

Ger*_*ica 17 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"
    }
}
Run Code Online (Sandbox Code Playgroud)

我不是针对像"因为它在JLS中这样指定的"这样的答案.我知道它是,因为JLS,12.4.1当初始化发生时读取只是:

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

  • ...

  • T是一个类,并且调用由T声明的静态方法.

  • ...

我很感兴趣是否有一个很好的理由为什么没有像这样的句子:

  • T是S的子类,在T上调用S声明的静态方法.

Dic*_*ici 10

在标题中要小心,静态字段和方法不会被继承.这意味着当您注释staticMethod()Sub,Sub.staticMethod()实际调用Super.staticMethod()然后Sub不执行静态初始化程序.

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

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

  • @Dici事实上,Eclipse中有一个警告:"Super类型的静态方法staticMethod()应​​该直接访问",并且可以在那里的"间接访问静态成员"首选项下停用它. (2认同)
  • 这样做的原因是,抽象类定义静态方法是相对常见的(虽然IMO可能不是一个好主意),它的子类可以在不导入它们的情况下使用它们.例如,在JUnit 3.x中,所有测试类都需要从`TestCase`继承,而`TestCase`扩展`junit.framework.Assert`,其唯一目的是将所有`assertEquals`等方法拉入子类的范围.既然我们有`import static`,那真的没有理由这样做. (2认同)

hei*_*don 3

我认为这与jvm 规范的这一部分有关:

\n
\n

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

\n

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

\n
\n

在jvm 规范的第 5 章中,他们还提到:\n类或接口 C 可能会由于以下原因而被初始化:

\n
\n

执行任何一个引用 C 的 Java 虚拟机指令 new、getstatic、putstatic 或 invokestatic(\xc2\xa7new、\xc2\xa7getstatic、\xc2\xa7putstatic、\xc2\xa7invokestatic)。这些指令通过字段引用或方法引用直接或间接引用类或接口。

\n

...

\n

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

\n
\n

在我看来,文档的第一部分指出,任何符号引用都只是简单地解析和调用,而不考虑它来自哪里。这个关于方法解析的文档对此有以下说明:

\n
\n

[M]方法解析尝试在 C 及其超类中定位引用的方法:

\n

如果 C 恰好声明了一个方法,其名称由方法引用指定,并且该声明是签名多态方法 (\xc2\xa72.9),则方法查找会成功。描述符中提到的所有类名均已解析 (\xc2\xa75.4.3.1)。

\n

解析的方法是签名多态方法声明。C 不需要使用方法引用指定的描述符来声明方法。

\n

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

\n

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

\n
\n

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

\n
\n

目的是类或接口类型具有一组初始值设定项,将其置于一致状态,并且该状态是其他类观察到的第一个状态。

\n
\n

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

\n

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

\n
Constant pool:\n    ...\n    #2 = Methodref          #18.#19        // Sub.staticMethod:()V\n\n... \n\nCode:\n  stack=0, locals=1, args_size=1\n     0: invokestatic  #2                  // Method Sub.staticMethod:()V\n     3: return\n
Run Code Online (Sandbox Code Playgroud)\n