反编译以下for-each循环的.class文件会产生有趣的结果。
来源 - Main.java:
public class Main {
public static void main(String[] args) {
String[] names = new String[3];
int var3 = 3;
for (String name : names) {
System.out.println(name);
}
}
}
结果 - Main.class:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Main {
public Main() {
}
public static void main(String[] args) {
String[] names = new String[3];
int var3 = true;
String[] var3 = names;
int var4 = names.length;
for(int var5 = 0; var5 < var4; ++var5) {
String name = var3[var5];
System.out.println(name);
}
}
}
该文件使用IntelliJ IDEA进行反编译。
true
分配给未使用的int
?var3
变量?这是代表反编译器的错误吗?
在字节码级别,没有正式的局部变量声明,至少不是源代码中已知的方式。方法具有同时存在的最大局部变量数量的声明或者为它们保留的“时隙”。当一个局部变量为其分配实际值时(通过“slot”索引)并且至少存在于该值的最后一次读取中。
通过这些操作,无法识别变量的范围何时结束,或者两个具有析取范围的变量是否共享一个槽(与同一变量的多个赋值相比)。好吧,如果他们有完全不兼容的类型,他们的任务给出了一个提示。
为了帮助调试,有一个可选的代码属性提供有关声明的局部变量及其范围的提示,但这不需要完整,也不会影响JVM执行字节码的方式。但是在这里,似乎该属性存在并且已被反编译器使用。
当我用javac -g
编译你的示例代码时,我得到了
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=7, args_size=1
0: iconst_3
1: anewarray #2 // class java/lang/String
4: astore_1
5: iconst_3
6: istore_2
7: aload_1
8: astore_3
9: aload_3
10: arraylength
11: istore 4
13: iconst_0
14: istore 5
16: iload 5
18: iload 4
20: if_icmpge 43
23: aload_3
24: iload 5
26: aaload
27: astore 6
29: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload 6
34: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: iinc 5, 1
40: goto 16
43: return
LocalVariableTable:
Start Length Slot Name Signature
29 8 6 name Ljava/lang/String;
0 44 0 args [Ljava/lang/String;
5 39 1 names [Ljava/lang/String;
7 37 2 var3 I
声明的变量args
(方法参数),names
,var3
和name
按顺序分配给变量索引0
,1
,2
和6
。
有没有声明的合成变量,
3
处保存对循环迭代的数组的引用4
保持数组长度5
处保存将在循环中递增的int
索引变量似乎,反编译器有一个简单的策略来处理LocalVariableTable
中没有包含的变量。它生成一个名称,该名称由前缀"var"
和堆栈帧中的索引组成。所以它为上面描述的合成变量生成了名称var3
,var4
和var5
,并不关心这些生成的名称和明确声明的名称之间存在名称冲突,即var3
。
现在,不清楚为什么反编译器为true
变量生成int
的赋值,但它有助于知道Java字节码中没有专用的boolean
处理指令,而是boolean
值的处理方式与int
值相同。它需要适当的元信息(如变量声明)来理解何时将值解释为boolean
值。也许,上面描述的名称冲突导致反编译器在之后混淆变量类型,最终认为值类型不是int
并且后来将其视为boolean
。但这只是猜测;可能还有一个完全不相关的错误。