Java 为什么是子类';在子类上调用其超类中声明的静态方法时未调用静态初始值设定项?

Java 为什么是子类';在子类上调用其超类中声明的静态方法时未调用静态初始值设定项?,java,inheritance,static,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();

鉴于以下类别:

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"
    }
}
我不是针对“因为JLS中规定了这样的问题”这样的答案。我知道是的,因为读到的只是:

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

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

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

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

注意标题,静态字段和方法不是继承的。这意味着,当您在
Sub
中注释
staticMethod()
时,
Sub.staticMethod()
实际上调用了
Super.staticMethod()
,那么
Sub
静态初始值设定项就不会执行

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


编辑:正如@GeroldBroser所指出的,这个答案的第一句话是错误的。静态方法也会被继承,但不会被重写,只是被隐藏。我把答案留给历史。

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


我相信,这是因为您没有为子类使用任何方法,所以jvm认为没有理由“初始化”类本身,方法调用在编译时静态绑定到父类-静态方法存在后期绑定

添加一些其他方法,并在子类之前调用它

Sub.someOtherMethod();
new UsersClass().method();
或者显式地
Class.forName(“Sub”)


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

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


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

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

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

你的例子似乎是人为的,设计得不好

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

我认为它与jvm规范的类型有关:

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

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

在jvm规范中,他们还提到: 除其他事项外,类或接口C可能由于以下原因而被初始化:

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

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

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

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

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

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

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

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

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

目的是一个类或接口类型有一组初始值设定项
Sub.someOtherMethod();
new UsersClass().method();
Class.forName("Sub");
new UsersClass().method();
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
    public class Sub extends Super {
    static {
        staticVar = new Object();
    }
    public static void staticMethod() {
        Super.staticMethod();
    }
}