解构 for-each 循环

5

反编译以下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反编译了该文件。

  • 为什么将未使用的int赋值为true
  • 为什么需要重新声明var3变量?

这是反编译工具的错误吗?


1
这是反编译器的错误吗?确实看起来是。实际上是两个错误。 - T.J. Crowder
当我使用 [JD Decompile] (https://java-decompiler.github.io/) 进行反编译时,我没有看到以下代码块:public static void main(String[] args) { String[] names = new String[3]; int var3 = 3; String[] arrayOfString1; int j = (arrayOfString1 = names).length; for (int i = 0; i < j; i++) { String name = arrayOfString1[i]; System.out.println(name); } } - royalghost
1
反编译器引入了以命名方案“var + 计数器”命名的变量。不幸的是,代码中存在一个名为“var3”的现有变量。由于jvm中没有布尔类型,因此单个布尔变量被存储和处理为int类型,导致出现了混乱的“int var3 = true”。它的目的让我困惑不解。 - Joop Eggen
1个回答

4
在字节码层面上,没有局部变量的正式声明,至少不像源代码中所知道的那样。方法有声明同时存在的局部变量的最大数量或“插槽”来为它们保留位置。当实际值被赋给局部变量(通过“插槽”索引)时,局部变量就会被创建并在至少读取该值的最后一个时刻存在。
使用这些操作,无法识别变量的范围何时结束,或者具有不同范围的两个变量是否共享插槽(与对同一变量的多次分配相比)。如果它们具有完全不兼容的类型,则它们的分配会给出提示。
为了帮助调试,还提供了一个可选的代码属性,提供有关声明的局部变量及其作用域的提示,但这不是必须的,并且不会影响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(方法参数),namesvar3name 分别被分配到变量索引 0126
存在未声明的合成变量:
  • 索引 3 用于保存循环迭代的数组的引用。
  • 索引 4 用于保存数组长度。
  • 索引 5 用于保存将在循环中递增的 int 索引变量。
看起来反编译器对不包含在 LocalVariableTable 中的变量有一个简单的处理策略。它生成一个名称,由前缀 "var" 和堆栈帧内的索引组成。因此,它为上述合成变量生成了名称 var3var4var5 ,并不关心这些生成的名称与显式声明的名称 var3 之间的名称冲突。
现在,为什么反编译器会为一个 int 变量赋值 true 不清楚,但需要知道的是,Java 字节码中没有专门的 boolean 处理指令,而是像处理 int 值一样处理 boolean 值。它需要适当的元信息(如变量声明)来理解何时应将值解释为 boolean 值。也许上述名称冲突导致反编译器在接下来混淆变量类型时产生困惑,最终将值类型视为非 int 类型,然后退回到将其视为 boolean 值进行处理。但这只是一个猜测;也可能完全与此无关的 bug 存在。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接