当构造函数没有显式调用超类构造函数(或this()
)时,编译器会插入super()
。
如果从类文件中删除此调用(编译后)会发生什么?
答案 0 :(得分:5)
我自己尝试过。
class Test
{
public Test()
{
System.out.println("Hello World");
}
public static void main(String[] args)
{
new Test()
}
}
我编译了它并使用类文件编辑器从构造函数中删除了invokespecial java/lang/Object/<init>()V
。
似乎JVM拒绝加载该类:
Exception in thread "main" java.lang.VerifyError: Operand stack overflow
Exception Details:
Location:
Test.<init>()V @4: ldc
Reason:
Exceeded max stack size.
Current Frame:
bci: @4
flags: { flagThisUninit }
locals: { uninitializedThis }
stack: { uninitializedThis, 'java/io/PrintStream' }
Bytecode:
0000000: 2ab2 0002 1203 b600 04b1
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
我仍然不知道这是否是一种定义的行为。
修改强>
根据Raedwald,我还必须改变堆栈操作。
所以我也删除了超级构造函数调用之前的aload_0
。
现在我收到以下异常:
Exception in thread "main" java.lang.VerifyError: Constructor must call super()
or this() before return
Exception Details:
Location:
org/exolin/geno/Test.<init>()V @8: return
Reason:
Error exists in the bytecode
Bytecode:
0000000: b200 0212 03b6 0004 b1
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
这让我很好奇所以我将构造函数指令重新排序为:
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Message"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
aload_0
invokespecial java/lang/Object/<init>()V
return
哪个有效!
答案 1 :(得分:3)
Java语言级别所需的内容与字节码级别所需的内容之间存在差异,这是非常宽松的。
在Java语言级别,您必须将构造函数调用作为第一个语句,但如果您将其删除,编译器将隐式插入一个。
在字节码级别,唯一的要求是在每个正常返回的代码路径上只有一个构造函数调用,而且在初始化之前对this
值可以执行的操作有限制。
特别是,这意味着
第一个选项实际上出现在Javac生成的代码中,因为在Java中,您可以将表达式作为参数传递给构造函数调用,并且计算该表达式的代码显然必须在调用构造函数之前发生。但其余的选择在Java中无法实现。
请注意,如果违反这些限制,如果从正常返回的普通Java构造函数中删除构造函数调用,将会发生这种情况,构造函数现在将无效,因为它返回但没有ctor调用。 因此,尝试加载类将在运行时因VerifyError而失败。
为了完整性,可以使用未初始化的this
值来执行此操作:将其与null相比较,并在同一个类中定义存储(但不读取)字段。
不多,对吗?但是,在ctor调用之前存储在同一个类中定义的字段的能力实际上是由Java编译器使用的。在字节码级别没有内部类的概念,因此当您在内部类中引用外部类时,编译器会在内部类中生成一个包含对外部类的引用的隐藏字段。为了确保在超类构造函数调用在内部类中重写并访问外部类的方法时,此方法正常工作,必须在超类构造函数调用之前初始化此隐藏字段。当然,当你自己编写字节码时,你可以做更多的事情。
答案 2 :(得分:3)
最新的JVM规范在4.9.2 Structural Constraints部分说明了这一点:
“每个实例初始化方法,除了从Object类的构造函数派生的实例初始化方法之外,必须调用此实例的另一个实例初始化方法或其实例成员之前的直接超类super的实例初始化方法访问“。
换句话说,如果删除与构造函数的invokespecial <init>
或super(...)
调用相对应的this(...)
指令序列,则类文件无效,验证者应该< / em>检测它。
(根据@Jimmy T的调查,确实如此!)
其他答案解释了这种约束背后的原因。