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

时间:2019-07-04 06:24:04

标签: java bytecode inner-classes

我正在研究内部类的工作原理,并在其内部的字节码内进行跟踪,无法理解为什么调用getClass()吗?
我找到了similar question for Lambda function,但听不懂。

我确实试图理解无空检查是必需的,在JDK 8之后,它已被称为requiredNoNull的静态函数取代。

代码:

class Outer{
      class Inner{
      }
      public static void main(String args[]){
            Outer.Inner obj = new Outer().new Inner();
      } 
}

ByteCode:

 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

3 个答案:

答案 0 :(得分:3)

It's a null check in disguise,仅此而已。但是,在那种特殊情况下,它并不是真正需要的,以后的ext { smackVersion = '4.3.2' supportLibVersion = '28.0.1' compileSdkVersion = 28 } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.1.0-rec01' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha02' implementation 'androidx.recyclerview:recyclerview:1.1.0-beta01' implementation 'androidx.legacy:legacy-support-core-ui:1.0.0' implementation 'com.google.android.material:material:1.1.0-alpha07' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2' implementation 'com.google.firebase:firebase-config:18.0.0' // Firebase implementation 'com.google.firebase:firebase-core:17.0.0' implementation 'com.google.firebase:firebase-messaging:19.0.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' //Smack implementation "org.igniterealtime.smack:smack-tcp:$smackVersion" implementation "org.igniterealtime.smack:smack-experimental:$smackVersion" implementation "org.igniterealtime.smack:smack-android:$smackVersion" implementation "org.igniterealtime.smack:smack-im:$smackVersion" 对此进行了一些优化-请看下面的示例。

也许这可以更好地解释该问题(使用java-12,其中javac的这种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作为参数传递,编译器无法确定传递的实例肯定不是null,因此Outer 将存在在字节码中。

答案 1 :(得分:2)

据我了解,@Eugene's answer是绝对正确的。我决定添加一个简单的解释。希望它将对某人有所帮助。

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

在更高版本的JDK中,空检查是changed to use更易读的Objects.requireNotNull。编译器也得到了改进,可以优化消除多余的空检查。

说明:

考虑如下代码:

class Outer{
      class Inner{
      }
      public static void main(String args[]){
            Outer.Inner obj = ((Outer) null).new Inner();
      } 
} 

此代码按应抛出NullPointerException。

问题是,从Java的角度来看,NPE仅是逻辑上的。构造函数在字节码级别上不存在。编译器生成的字节代码大致等于以下伪代码:

class Outer {
    public static void main(String[] args) {
         Outer tmp = (Outer) null;
         Outer.Inner obj = new; //object created
         tmp."<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)从Object.getClass中删除对Outer.class的调用。
  4. 再次运行该程序,它应该成功完成。

答案 2 :(得分:-1)

作为外部类中存在的内部类的定义,JVM需要首先加载外部类。