Java: 对象 construction/initialization 可以延迟吗?
Java: object construction/initialization can be deferred?
我正在做一些 android 项目,我正在混合 Java 和 Kotlin。我在 Java 中反编译了一段 Kotlin 代码,以查看它实际上是如何转换的。
Kotlin 代码
fun postSettingToServer() {
val request = CoockieJsonRequest(Request.Method.POST, URLBuilder.GetPushSettings(this), pushModel!!.toJSON(), null, null)
VolleySingleton.getInstance(applicationContext).addToRequestQueue(request)
}
Android 工作室创建Java 等效
public final void postSettingToServer() {
CoockieJsonRequest var10000 = new CoockieJsonRequest;
String var10003 = URLBuilder.GetPushSettings((Context)this);
Intrinsics.checkExpressionValueIsNotNull(var10003, "URLBuilder.GetPushSettings(this)");
PushSettings var10004 = this.pushModel;
if (var10004 == null) {
Intrinsics.throwNpe();
}
var10000.<init>(1, var10003, var10004.toJSON(), (Listener)null, (ErrorListener)null);
CoockieJsonRequest request = var10000;
VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue((Request)request);
}
困扰我的是这个CoockieJsonRequest var10000 = new CoockieJsonRequest;
。所以,基本上,在这里我们可以看到代码使用 new
运算符将内存分配给 CoockieJsonRequest
,但不要将其称为构造函数(没有大括号)。取而代之的是,代码执行了一些其他操作(展开 pushModel
对象),然后才使用 JVM <init>
初始化 CoockieJsonRequest
。这对我来说真的很奇怪,因为我一直认为对象必须在分配时构造。
所以,我的问题是它是如何工作的(可以推迟构建)或者 Android Studion Kotlin 反编译器有问题,它只会创建奇怪的反编译输出?
TL;DR: 这几乎是一个正常的实例构造,但是条件语句混淆了反编译器。
在字节码级别,这是两条不同的指令。 new
创建给定 class 的未初始化实例。 invokespecial
,引用 <init>
方法,使用给定的构造函数初始化实例。绝不是强制性的,他们必须立即相互关注。
您看到的代码大致匹配以下 java 代码(在内联合成变量之后):
CoockieJsonRequest request = new CoockieJsonRequest(1,
URLBuilder.GetPushSettings((Context)this),
this.pushModel.toJSON(),
(Listener) null,
(ErrorListener) null);
Java编译器
好像很正常
- 先用
new
指令创建实例,
- 然后准备构造函数的参数,
- 最后使用
invokespecial
指令调用构造函数<init>
方法。
因此,使用 Java 编译器,从上面创建的 Java 实例可能会产生非常相似的字节码序列,因此也会产生相似的反编译。
只有 Intrinsics
空值检查会影响字节码,并且可能会使反编译器感到困惑,以至于它无法将参数表达式内联到“正常”构造函数调用中。
例如当试图内联 var10004
表达式时,if
构造最多可以用三元运算符替换,从而使生成的 Java 代码即使不是不可能,至少也很复杂。所以,反编译器在这里失败是很合理的。
在字节码级别,可以在对象的实例化和构造函数的调用之间放置任意代码,只要遵守 中描述的约束即可。
通常,new Type(expression)
形式的表达式被编译为 Type
的实例化,然后是 expression
的代码,然后调用构造函数,传递前面对表达式求值的结果。
但是在字节码层面上,表达式和语句是没有区别的。即使在源级别上,区别也很模糊。例如,对于 Java 14,您可以使用以下结构在表达式中包含语句:
public static void main(String[] args) {
new String(switch(0) {
default:
try {
yield Files.readAllBytes(Path.of(""));
}
catch(IOException ex) {
yield ex.toString().getBytes();
}
});
showBytecode();
}
private static void showBytecode() {
ToolProvider.findFirst("javap")
.ifPresent(tp -> tp.run(System.out, System.err, "-c", Tmp.class.getName()));
}
编译为
public static void main(java.lang.String[]);
Code:
0: new #1 // class java/lang/String
3: dup
4: astore_1
5: astore_2
6: iconst_0
7: lookupswitch { // 0
default: 16
}
16: ldc #3 // String
18: iconst_0
19: anewarray #1 // class java/lang/String
22: invokestatic #5 // InterfaceMethod java/nio/file/Path.of:(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path;
25: invokestatic #11 // Method java/nio/file/Files.readAllBytes:(Ljava/nio/file/Path;)[B
28: astore_3
29: aload_2
30: aload_1
31: aload_3
32: goto 52
35: astore 4
37: aload 4
39: invokevirtual #19 // Method java/io/IOException.toString:()Ljava/lang/String;
42: invokevirtual #23 // Method java/lang/String.getBytes:()[B
45: astore_3
46: aload_2
47: aload_1
48: aload_3
49: goto 52
52: invokespecial #27 // Method java/lang/String."<init>":([B)V
55: pop
56: invokestatic #31 // Method showBytecode:()V
59: return
Exception table:
from to target type
16 29 35 Class java/io/IOException
请注意在偏移量 #0 处的指令中创建的实例是如何在偏移量 #52 处初始化的,中间有相当复杂的指令。
我正在做一些 android 项目,我正在混合 Java 和 Kotlin。我在 Java 中反编译了一段 Kotlin 代码,以查看它实际上是如何转换的。
Kotlin 代码
fun postSettingToServer() {
val request = CoockieJsonRequest(Request.Method.POST, URLBuilder.GetPushSettings(this), pushModel!!.toJSON(), null, null)
VolleySingleton.getInstance(applicationContext).addToRequestQueue(request)
}
Android 工作室创建Java 等效
public final void postSettingToServer() {
CoockieJsonRequest var10000 = new CoockieJsonRequest;
String var10003 = URLBuilder.GetPushSettings((Context)this);
Intrinsics.checkExpressionValueIsNotNull(var10003, "URLBuilder.GetPushSettings(this)");
PushSettings var10004 = this.pushModel;
if (var10004 == null) {
Intrinsics.throwNpe();
}
var10000.<init>(1, var10003, var10004.toJSON(), (Listener)null, (ErrorListener)null);
CoockieJsonRequest request = var10000;
VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue((Request)request);
}
困扰我的是这个CoockieJsonRequest var10000 = new CoockieJsonRequest;
。所以,基本上,在这里我们可以看到代码使用 new
运算符将内存分配给 CoockieJsonRequest
,但不要将其称为构造函数(没有大括号)。取而代之的是,代码执行了一些其他操作(展开 pushModel
对象),然后才使用 JVM <init>
初始化 CoockieJsonRequest
。这对我来说真的很奇怪,因为我一直认为对象必须在分配时构造。
所以,我的问题是它是如何工作的(可以推迟构建)或者 Android Studion Kotlin 反编译器有问题,它只会创建奇怪的反编译输出?
TL;DR: 这几乎是一个正常的实例构造,但是条件语句混淆了反编译器。
在字节码级别,这是两条不同的指令。 new
创建给定 class 的未初始化实例。 invokespecial
,引用 <init>
方法,使用给定的构造函数初始化实例。绝不是强制性的,他们必须立即相互关注。
您看到的代码大致匹配以下 java 代码(在内联合成变量之后):
CoockieJsonRequest request = new CoockieJsonRequest(1,
URLBuilder.GetPushSettings((Context)this),
this.pushModel.toJSON(),
(Listener) null,
(ErrorListener) null);
Java编译器
好像很正常- 先用
new
指令创建实例, - 然后准备构造函数的参数,
- 最后使用
invokespecial
指令调用构造函数<init>
方法。
因此,使用 Java 编译器,从上面创建的 Java 实例可能会产生非常相似的字节码序列,因此也会产生相似的反编译。
只有 Intrinsics
空值检查会影响字节码,并且可能会使反编译器感到困惑,以至于它无法将参数表达式内联到“正常”构造函数调用中。
例如当试图内联 var10004
表达式时,if
构造最多可以用三元运算符替换,从而使生成的 Java 代码即使不是不可能,至少也很复杂。所以,反编译器在这里失败是很合理的。
在字节码级别,可以在对象的实例化和构造函数的调用之间放置任意代码,只要遵守
通常,new Type(expression)
形式的表达式被编译为 Type
的实例化,然后是 expression
的代码,然后调用构造函数,传递前面对表达式求值的结果。
但是在字节码层面上,表达式和语句是没有区别的。即使在源级别上,区别也很模糊。例如,对于 Java 14,您可以使用以下结构在表达式中包含语句:
public static void main(String[] args) {
new String(switch(0) {
default:
try {
yield Files.readAllBytes(Path.of(""));
}
catch(IOException ex) {
yield ex.toString().getBytes();
}
});
showBytecode();
}
private static void showBytecode() {
ToolProvider.findFirst("javap")
.ifPresent(tp -> tp.run(System.out, System.err, "-c", Tmp.class.getName()));
}
编译为
public static void main(java.lang.String[]);
Code:
0: new #1 // class java/lang/String
3: dup
4: astore_1
5: astore_2
6: iconst_0
7: lookupswitch { // 0
default: 16
}
16: ldc #3 // String
18: iconst_0
19: anewarray #1 // class java/lang/String
22: invokestatic #5 // InterfaceMethod java/nio/file/Path.of:(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path;
25: invokestatic #11 // Method java/nio/file/Files.readAllBytes:(Ljava/nio/file/Path;)[B
28: astore_3
29: aload_2
30: aload_1
31: aload_3
32: goto 52
35: astore 4
37: aload 4
39: invokevirtual #19 // Method java/io/IOException.toString:()Ljava/lang/String;
42: invokevirtual #23 // Method java/lang/String.getBytes:()[B
45: astore_3
46: aload_2
47: aload_1
48: aload_3
49: goto 52
52: invokespecial #27 // Method java/lang/String."<init>":([B)V
55: pop
56: invokestatic #31 // Method showBytecode:()V
59: return
Exception table:
from to target type
16 29 35 Class java/io/IOException
请注意在偏移量 #0 处的指令中创建的实例是如何在偏移量 #52 处初始化的,中间有相当复杂的指令。