给定以下类别:
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将在以下任何一种首次出现之前立即初始化:
…
T是一个类,并调用T声明的静态方法。
我对没有这样的句子是否有充分的理由感兴趣:
T是S的子类,并且在T上调用S声明的静态方法。
我认为这与jvm规范的这一部分有关:
每个框架(第2.6节)都包含对当前方法类型的运行时常量池(第2.5.5节)的引用,以支持方法代码的动态链接。方法的类文件代码是指要调用的方法和要通过符号引用访问的变量。动态链接将这些符号方法引用转换为具体的方法引用,根据需要加载类以解析尚未定义的符号,并将变量访问转换为与这些变量的运行时位置关联的存储结构中的适当偏移量。 方法和变量的这种较晚的绑定使方法使用的其他类中的更改不太可能破坏此代码。
每个框架(第2.6节)都包含对当前方法类型的运行时常量池(第2.5.5节)的引用,以支持方法代码的动态链接。方法的类文件代码是指要调用的方法和要通过符号引用访问的变量。动态链接将这些符号方法引用转换为具体的方法引用,根据需要加载类以解析尚未定义的符号,并将变量访问转换为与这些变量的运行时位置关联的存储结构中的适当偏移量。
方法和变量的这种较晚的绑定使方法使用的其他类中的更改不太可能破坏此代码。
在jvm规范的第5章中,他们还提到:由于以下原因,可能会初始化类或接口C:
执行引用C的Java虚拟机指令new,getstatic,putstatic或invokestatic的任何一条(§new,§getstatic,§putstatic,§invokestatic)。这些指令通过字段引用或方法引用 直接或间接 引用类或接口。 … 在执行getstatic,putstatic或invokestatic指令后,如果尚未 解析已声明字段或方法的类或接口, 则将对其进行初始化。
执行引用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具有超类,则对C的直接超类递归调用方法解析的步骤2。
[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