我正在研究内部类的工作原理,并在其内部的字节码内进行跟踪,无法理解为什么调用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
答案 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
您如何检查确实是以下原因:
Outer
类。Object.getClass
中删除对Outer.class
的调用。答案 2 :(得分:-1)
作为外部类中存在的内部类的定义,JVM需要首先加载外部类。