Java 如果我从类文件中删除超级构造函数调用,会发生什么?
当构造函数没有超类构造函数(或Java 如果我从类文件中删除超级构造函数调用,会发生什么?,java,constructor,.class-file,Java,Constructor,.class File,当构造函数没有超类构造函数(或this())的显式调用时,编译器将插入super() 如果从类文件中删除此调用(编译后),会发生什么情况?我自己也尝试过 class Test { public Test() { System.out.println("Hello World"); } public static void main(String[] args) { new Test() } } 我编译了它,并用类
this()
)的显式调用时,编译器将插入super()
如果从类文件中删除此调用(编译后),会发生什么情况?我自己也尝试过
class Test
{
public Test()
{
System.out.println("Hello World");
}
public static void main(String[] args)
{
new Test()
}
}
我编译了它,并用类文件编辑器从构造函数中删除了invokespecial java/lang/Object/()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)
这让我很好奇,因此我将构造函数指令重新排序为:
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
getstaticjava/lang/System/out Ljava/io/PrintStream;
最不发达国家“信息”
invokeVirtualJava/io/PrintStream/println(Ljava/lang/String;)V
阿洛德0
调用特殊的java/lang/Object/()V
返回
真管用 Java语言级别的要求与字节码级别的要求之间存在区别,字节码级别的要求要宽松得多 在Java语言级别,必须有一个构造函数调用作为第一个语句,但是如果不使用它,编译器将隐式插入一个 在字节码级别,唯一的要求是在每个正常返回的代码路径上只有一个构造函数调用,并且在初始化之前,对
这个
值的操作有限制
特别是,这意味着
- 构造函数调用不必出现在方法的开头
- 您可以选择基于条件逻辑调用不同的构造函数
- 在构造函数中可以有异常处理程序,包括捕获超类构造函数引发的异常
- 如果每个代码路径都抛出异常或进入无限循环,那么就根本没有构造函数调用
this
值执行的操作:将其与null进行比较,并存储(但不读取)在同一类中定义的字段
不多,对吧?但是,Java编译器实际上使用了在ctor调用之前存储在同一类中定义的字段的能力。在字节码级别没有内部类的概念,因此当您在内部类中引用外部类时,编译器会在内部类中生成一个隐藏字段,其中包含对外部类的引用。为了确保在超类构造函数调用在内部类中被重写并访问外部类的方法时,此隐藏字段可以正常工作,必须在超类构造函数调用之前初始化此隐藏字段。当然,当您自己编写字节码时,还可以做更多的事情。最新的JVM规范在本节中说: 每个实例初始化方法(从类对象的构造函数派生的实例初始化方法除外)在访问其实例成员之前,必须调用该类的另一个实例初始化方法或其直接超类super的实例初始化方法 换句话说,如果删除与构造函数的
super(…)
或此(…)
调用相对应的invokespecial
指令序列,则类文件无效,验证器应该检测到它
(根据@Jimmy T的调查,确实如此!)
此约束背后的原因由其他答案解释。首先,未定义的行为。为什么要实现?调用超级类的其他构造函数或使用默认构造函数,您需要调用至少一个其他wise对象,否则将无法构造。@Braj我认为这是出于智力好奇心,没有理由这么做,但结果可能很有趣。如果从类文件中删除此调用,会发生什么?更简单的方法是编辑字节码,运行并找出JVM的反应,而不是让人们在这里猜测。@EJP,不,这不是未定义的行为。事实上,它是在JVM规范中指定的。“超过了最大堆栈大小”。这表明附近用于操作堆栈的字节码也需要修改。是一个有关字节码的哪些部分需要操作的相关问题。
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