为什么在子类上调用在其超类中声明的静态方法时,不会调用子类的静态初始化程序? [英] Why is a subclass' static initializer not invoked when a static method declared in its superclass is invoked on the subclass?

查看:128
本文介绍了为什么在子类上调用在其超类中声明的静态方法时,不会调用子类的静态初始化程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出以下类:

  public abstract class Super {
protected static Object staticVar;

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

公共类Sub extends Super {
static {
staticVar = new Object();
}

//在这里声明一个具有相同签名的方法,
//因此隐藏了Super.staticMethod(),避免staticVar为null
/ *
public static void staticMethod(){
Super.staticMethod();
}
* /
}

公共类UserClass {
public static void main(String [] args){
new UserClass( )。方法();
}

void method(){
Sub.staticMethod(); //打印null
}
}

我没有定位在答案中,例如因为它在JLS中就像这样指定了。我知道,因为 JLS,12.4.1发生初始化时只读:


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




  • ...


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


  • ...





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



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



解决方案

我认为它与 th是jvm规范的一部分


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



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


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


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



...



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


在我看来,第一篇文档说明任何符号引用只是简单地解析和调用,而不考虑它来自何处。这个有关方法解决的文档有以下说法:


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

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



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



否则,如果C声明了一个方法,该方法具有方法指定的名称和描述符参考,方法查找成功。



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


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


意图是类或接口类型具有一组初始值设定项,使其保持一致状态,并且此状态是其他类观察到的第一个状态。


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



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

 常量池:
...
#2 = Methodref#18。#19 // Sub.staticMethod :() V

...

代码:
stack = 0,locals = 1,args_size = 1
0:invokestatic#2 // Method Sub .staticMethod :()V
3:返回


Given the following classes:

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"
    }
}

I'm not targeting at answers like "Because it's specified like this in the JLS.". I know it is, since JLS, 12.4.1 When Initialization Occurs reads just:

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • ...

  • T is a class and a static method declared by T is invoked.

  • ...

I'm interested in whether there is a good reason why there is not a sentence like:

  • T is a subclass of S and a static method declared by S is invoked on T.

解决方案

I think it has to do with this part of the jvm spec:

Each frame (§2.6) contains a reference to the run-time constant pool (§2.5.5) for the type of the current method to support dynamic linking of the method code. The class file code for a method refers to methods to be invoked and variables to be accessed via symbolic references. Dynamic linking translates these symbolic method references into concrete method references, loading classes as necessary to resolve as-yet-undefined symbols, and translates variable accesses into appropriate offsets in storage structures associated with the run-time location of these variables.

This late binding of the methods and variables makes changes in other classes that a method uses less likely to break this code.

In chapter 5 in the jvm spec they also mention: A class or interface C may be initialized, among other things, as a result of:

The execution of any one of the Java Virtual Machine instructions new, getstatic, putstatic, or invokestatic that references C (§new, §getstatic, §putstatic, §invokestatic). These instructions reference a class or interface directly or indirectly through either a field reference or a method reference.

...

Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already.

It seems to me the first bit of documentation states that any symbolic reference is simply resolved and invoked without regard as to where it came from. This documentation about method resolution has the following to say about that:

[M]ethod resolution attempts to locate the referenced method in C and its superclasses:

If C declares exactly one method with the name specified by the method reference, and the declaration is a signature polymorphic method (§2.9), then method lookup succeeds. All the class names mentioned in the descriptor are resolved (§5.4.3.1).

The resolved method is the signature polymorphic method declaration. It is not necessary for C to declare a method with the descriptor specified by the method reference.

Otherwise, if C declares a method with the name and descriptor specified by the method reference, method lookup succeeds.

Otherwise, if C has a superclass, step 2 of method resolution is recursively invoked on the direct superclass of C.

So the fact that it's called from a subclass seems to simply be ignored. Why do it this way? In the documentation you provided they say:

The intent is that a class or interface type has a set of initializers that put it in a consistent state, and that this state is the first state that is observed by other classes.

In your example, you alter the state of Super when Sub is statically initialized. If initialization happened when you called Sub.staticMethod you would get different behavior for what the jvm considers the same method. This might be the inconsistency they were talking about avoiding.

Also, here's some of the decompiled class file code that executes staticMethod, showing use of 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

这篇关于为什么在子类上调用在其超类中声明的静态方法时,不会调用子类的静态初始化程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆