为什么我们为 Inner class 创建对象时会调用 getClass()?
Why is getClass() called when we create an object for Inner class?
我正在研究 Inner Class 的工作原理,在它的字节码中,我正在跟踪堆栈,但不明白为什么调用 getClass()?
我找到了一个 但无法理解它。
我确实试图理解没有空检查是必需的,在 JDK 8 之后它被一个名为 requiredNoNull 的静态函数所取代。
代码:
class Outer{
class Inner{
}
public static void main(String args[]){
Outer.Inner obj = new Outer().new Inner();
}
}
字节码:
public static void main(java.lang.String[]);
Code:
0: new #2 // class Outer$Inner
3: dup
4: new #3 // class Outer
7: dup
8: invokespecial #4 // Method "<init>":()V
11: dup
12: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
15: pop
16: invokespecial #6 // Method Outer$Inner."<init>":(LOuter;)V
19: astore_1
It's a null check in disguise,仅此而已。不过,在那种特殊情况下,它并不是真正需要的,未来 javac
会对其进行一些优化 - 请看下面的示例。
可能这会更好地解释问题(使用 java-12,其中 getClass
hack 已被 Objects::requireNonNull
取代):
public class Outer {
class Inner {
}
public void left() {
Outer.Inner inner = new Outer().new Inner();
}
public void right(Outer outer) {
Outer.Inner inner = outer.new Inner();
}
}
left
方法将编译成某种东西(您可以自己查看字节码),不会 使用 Objects::requireNonNull
,因为 Outer
发生在适当的位置,编译器可以肯定地告诉 new Outer()
实例不是 null
.
另一方面,你将 Outer
作为参数传递,编译器无法证明传递的实例肯定不为 null,因此 Objects::requireNonNull
将是在字节码中存在。
据我了解,完全正确。我决定用简单的话添加一个解释。希望它能对某人有所帮助。
答案: 编译器在 JDK8 中使用对 Object.getClass
的调用在必要时生成 NullPointerExceptions。在您的示例中,此检查是不必要的,因为 new Outer()
不能为 null,但编译器不够智能,无法确定它。
在 JDK 的更高版本中,空检查 changed to use 更具可读性 Objects.requireNotNull
。编译器也得到了改进,以优化多余的 null 检查。
解释:
考虑这样的代码:
class Outer{
class Inner{
}
public static void main(String args[]){
Outer.Inner obj = ((Outer) null).new Inner();
}
}
这段代码会抛出 NullPointerException,这是应该的。
问题是,NPE 仅从 Java 的角度来看是合乎逻辑的。字节码级别不存在构造函数。编译器生成的字节码或多或少等同于以下伪代码:
class Outer {
public static void main(String[] args) {
Outer tmp = (Outer) null;
Outer$Inner obj = new; //object created
obj."<init>"(tmp);
}
}
class Outer$Inner {
//generated field
private final Outer outer;
//generated initializer
void "<init>"(Outer outer) {
this.outer = outer;
}
}
如您所见,构造函数已替换为方法。而且该方法本身不会检查它的参数是否为 null,因此不会抛出异常。
因此,编译器必须添加额外的空检查来生成 NullPointerException
。在 Java 8 之前,实现此目的的快速而肮脏的方法是发出对 getClass
:
的调用
Outer tmp = (Outer) null;
tmp.getClass(); //generates an NPE
你如何检查这确实是原因:
- 使用 JDK 8.
编译上面的 Outer
class
- 运行它,它应该抛出一个NPE。
- 使用任何字节码编辑器(例如 JBE)从
Outer.class
中删除对 Object.getClass
的调用。
- 运行 程序再次运行,它应该成功完成。
我正在研究 Inner Class 的工作原理,在它的字节码中,我正在跟踪堆栈,但不明白为什么调用 getClass()?
我找到了一个
我确实试图理解没有空检查是必需的,在 JDK 8 之后它被一个名为 requiredNoNull 的静态函数所取代。
代码:
class Outer{
class Inner{
}
public static void main(String args[]){
Outer.Inner obj = new Outer().new Inner();
}
}
字节码:
public static void main(java.lang.String[]);
Code:
0: new #2 // class Outer$Inner
3: dup
4: new #3 // class Outer
7: dup
8: invokespecial #4 // Method "<init>":()V
11: dup
12: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
15: pop
16: invokespecial #6 // Method Outer$Inner."<init>":(LOuter;)V
19: astore_1
It's a null check in disguise,仅此而已。不过,在那种特殊情况下,它并不是真正需要的,未来 javac
会对其进行一些优化 - 请看下面的示例。
可能这会更好地解释问题(使用 java-12,其中 getClass
hack 已被 Objects::requireNonNull
取代):
public class Outer {
class Inner {
}
public void left() {
Outer.Inner inner = new Outer().new Inner();
}
public void right(Outer outer) {
Outer.Inner inner = outer.new Inner();
}
}
left
方法将编译成某种东西(您可以自己查看字节码),不会 使用 Objects::requireNonNull
,因为 Outer
发生在适当的位置,编译器可以肯定地告诉 new Outer()
实例不是 null
.
另一方面,你将 Outer
作为参数传递,编译器无法证明传递的实例肯定不为 null,因此 Objects::requireNonNull
将是在字节码中存在。
据我了解,
答案: 编译器在 JDK8 中使用对 Object.getClass
的调用在必要时生成 NullPointerExceptions。在您的示例中,此检查是不必要的,因为 new Outer()
不能为 null,但编译器不够智能,无法确定它。
在 JDK 的更高版本中,空检查 changed to use 更具可读性 Objects.requireNotNull
。编译器也得到了改进,以优化多余的 null 检查。
解释:
考虑这样的代码:
class Outer{
class Inner{
}
public static void main(String args[]){
Outer.Inner obj = ((Outer) null).new Inner();
}
}
这段代码会抛出 NullPointerException,这是应该的。
问题是,NPE 仅从 Java 的角度来看是合乎逻辑的。字节码级别不存在构造函数。编译器生成的字节码或多或少等同于以下伪代码:
class Outer {
public static void main(String[] args) {
Outer tmp = (Outer) null;
Outer$Inner obj = new; //object created
obj."<init>"(tmp);
}
}
class Outer$Inner {
//generated field
private final Outer outer;
//generated initializer
void "<init>"(Outer outer) {
this.outer = outer;
}
}
如您所见,构造函数已替换为方法。而且该方法本身不会检查它的参数是否为 null,因此不会抛出异常。
因此,编译器必须添加额外的空检查来生成 NullPointerException
。在 Java 8 之前,实现此目的的快速而肮脏的方法是发出对 getClass
:
Outer tmp = (Outer) null;
tmp.getClass(); //generates an NPE
你如何检查这确实是原因:
- 使用 JDK 8. 编译上面的
- 运行它,它应该抛出一个NPE。
- 使用任何字节码编辑器(例如 JBE)从
Outer.class
中删除对Object.getClass
的调用。 - 运行 程序再次运行,它应该成功完成。
Outer
class