为什么我们为 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

你如何检查这确实是原因:

  1. 使用 JDK 8.
  2. 编译上面的 Outer class
  3. 运行它,它应该抛出一个NPE。
  4. 使用任何字节码编辑器(例如 JBE)从 Outer.class 中删除对 Object.getClass 的调用。
  5. 运行 程序再次运行,它应该成功完成。