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