当前位置: 首页 > 知识库问答 >
问题:

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

杨良平
2023-03-14

给定以下类:

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中规定了这样的问题”这样的答案。我知道是这样的,因为JLS 12.4.1在初始化时只读取:

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

>

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

    ...

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

    • T是S的子类,在T上调用由S声明的静态方法
  • 共有3个答案

    惠洛华
    2023-03-14

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

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

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

    jvm规范的第5章中,他们还提到:类或接口C可能会被初始化,其中包括:

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

    ...

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

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

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

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

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

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

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

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

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

    在您的示例中,当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
    
    宰父子安
    2023-03-14

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

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

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

    凤衡
    2023-03-14

    注意标题,静态字段和方法不会被继承。这意味着,当您在Sub中注释static method()时,Sub.static方法()实际上调用Super.static方法()然后Sub静态初始化器不是执行。

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

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

     类似资料:
    • 问题内容: 给定以下类别: 我的目标不是像“因为在JLS中这样指定”这样的答案。我知道是的,因为JLS是12.4.1发生初始化时,其读取内容仅为: 类或接口类型T将在以下任何一种首次出现之前立即初始化: … T是一个类,并调用T声明的静态方法。 … 我对没有这样的句子是否有充分的理由感兴趣: T是S的子类,并且在T上调用S声明的静态方法。 问题答案: 我认为这与jvm规范的这一部分有关: 每个框架

    • 问题内容: 说,我有一个带有SomeType的具有静态方法的Class对象的引用。有没有一种方法可以调用该方法而不先实例化SomeType?最好不要转义强类型。 编辑:好的,我搞砸了。 在这种情况下,someMethod()始终不能是静态的。 问题答案: 根据定义,静态方法是在类上调用的,而不是在该类的实例上调用的。 因此,如果您使用: 您没有实例化任何东西(不考虑由JVM处理并且超出了您的范围的

    • 我认为只有Base类在调用时才被初始化。当我们在Base中有一个(非编译时间常量)静态字段,而不是静态方法时,就会发生这种情况。我的疑问只是JLS对此不太清楚。 根据JLS(见下文),似乎只有基类被初始化。但我是否正确阅读和理解JLS? 我还知道,在(非编译时间常量)静态字段的情况下,只有定义静态字段的类才被初始化(称为静态初始化器): 12.4.1. 发生初始化时(JLS) 类或接口类型T将在第

    • 假设我有一个基类(具有一个名为)的虚拟方法)和300个子类:每个子类都有一个静态方法,外加一个重写。(请不要问为什么;这是在一个生产软件中,已经给出了,不能为了更好的重用而更改设计。实际上,那些子类是由一个代码生成器生成的,但现在这已经不相关了。) 根据应用程序的不同执行,需要初始化的(小)子集。换句话说,有一些数据是特定的所有实例共同共享或访问的。显然,将这些实体定义为成员/方法(因为它们由的所

    • 问题内容: 因此,问题或多或少是我写的。我知道可能还不清楚,所以我举一个例子。 我有Tree类,其中有Node类,并且Tree的空构造函数被编写为: Eclipse给我一个错误:空构造函数中的“ new RBTree()”没有“由于某些中间构造函数调用而导致RBTree类型的封闭实例不可用”。但是,如果将RBNode更改为静态类,则没有问题。 那么,为什么在类为静态的时候它可以工作。 顺便说一句,

    • 这是我的代码: 还有我的测试,我单独运行。 当我运行test foo时,我将看到: 但是当我运行测试栏时,我看到的是: 引用本页内容。。 类对象由Java虚拟机在加载类时自动构造,并通过调用类加载器中的defineClass方法来构造。 所以我的理解是,在测试条中,愚蠢的类被加载,否则我会看到一个空的,我猜?所以类对象被创建,因为类本身被加载。。 现在引用这一页 静态初始化块在JVM(类加载器-具