繁体   English   中英

为什么在为内部类创建对象时调用 getClass()?

[英]Why is getClass() called when we create an object for Inner class?

我正在研究内部类的工作原理及其字节码,我正在跟踪堆栈,但不明白为什么调用 getClass()?
我发现了一个关于 Lambda 函数类似问题,但无法理解。

我确实试图理解没有空检查是必需的,在 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

这是一个伪装的空检查,仅此而已。 不过,在这种特殊情况下,它并不是真正需要的,并且未来的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将出现在字节码中。

据我了解, @Eugene 的回答是绝对正确的。 我决定用简单的话添加一个解释。 希望它会帮助某人。

答: JDK8 中的编译器使用对Object.getClass调用来在必要时生成 NullPointerExceptions。 在您的示例中,此检查是不必要的,因为new Outer()不能为 null,但编译器不够聪明,无法确定它。

在 JDK 的更高版本中,空检查被更改为使用更具可读性的Objects.requireNotNull 编译器也得到了改进以优化多余的空检查。

解释:

考虑这样的代码:

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 编译上面的Outer类。
  2. 运行它,它应该抛出一个 NPE。
  3. 使用任何字节码编辑器(例如JBE )从Outer.class删除对Object.getClass的调用。
  4. 再次运行程序,它应该成功完成。

作为Outer类中存在的Inner类的定义,JVM需要首先加载Outer类。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM