【问题标题】:Why is the int in try compiled into byte为什么try中的int编译成byte
【发布时间】:2020-07-25 10:59:41
【问题描述】:

我发现一个现象:

public class TryTest {
    public static void main(String[] args) {
        System.out.println(test1());
    }

    public static int test1() {
        try {
            int a = 127;
            return a;
        } catch (Exception e) {

        }finally {
            System.out.println("I am finally");
        }
        return 0;
    }
}

编译成.class:

public class TryTest {
    public TryTest() {
    }

    public static void main(String[] args) {
        System.out.println(test1());
    }

    public static int test1() {
        try {
            int a = 127;
            byte var1 = a;
            return var1;
        } catch (Exception var5) {
        } finally {
            System.out.println("I am finally");
        }

        return 0;
    }
}

为什么“int a”转换为“byte var1”? 是为了节省内存吗? 这不是不必要的吗? 我想知道编译器是如何处理这个的。
但我发现如果这样的代码:

    public static int test3() {
        int a = 1;
        return a;
    }

编译成.class:

    public static int test3() {
        int a = 1;
        return a;
    }

如果没有“try”,则不会编译成“byte”

【问题讨论】:

    标签: java int try-catch byte


    【解决方案1】:

    如果您想查看在 Java 中编译成的内容,您不应该查看反编译的代码。将.class 文件转换回.java 文件涉及大量解释(甚至可以说是猜测),不应将其视为实际编译情况的指示。请查看javap -v 输出,它会显示实际的字节码。你方法的字节码是这样的(我删除了一些不必要的细节,自己运行检查一下):

      public static int test1();
        descriptor: ()I
        flags: (0x0009) ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=0
             0: bipush        127
             2: istore_0
             3: iload_0
             4: istore_1
             5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             8: ldc           #5                  // String I am finally
            10: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            13: iload_1
            14: ireturn
            15: astore_0
            16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            19: ldc           #5                  // String I am finally
            21: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            24: goto          38
            27: astore_2
            28: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            31: ldc           #5                  // String I am finally
            33: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            36: aload_2
            37: athrow
            38: iconst_0
            39: ireturn
          Exception table:
             from    to  target type
                 0     5    15   Class java/lang/Exception
                 0     5    27   any
    

    其中没有任何内容表明任何内容都存储在 byte 中(iload_*istore_* 加载和存储整数值)。

    唯一的字节特定指令是bipush,它会压入一个字节值,但它会在堆栈上扩展为int 值。这只是比sipush(推送short 常量)或ldc(需要将值存储在常量池中)节省几个字节。

    istore_1 用于记住在执行 finally 块时要返回的值 (8-10)。然后我们使用iload_1将其加载回来并返回。

    反编译器可能认为这是一个实际变量,而它只是由 try-catch-finally 构造引起的合成构造。

    此外,您可能会注意到字节码看起来非常低效,但这仅仅是因为javac 编译器将非常直接的转换为字节码并且实际上并没有进行任何优化。任何实际的优化(例如从不实际将 any 值存储在 any 变量中,而只是简单地返回常量值 127)将由 JVM 在运行时完成。

    【讨论】:

      【解决方案2】:

      Java 反编译器因从字节码生成外观奇特、不正确甚至不可编译的 Java 代码而臭名昭著。通过查看反编译的代码,您无法推断 Java 编译器以特定方式编译了 Java 源文件。

      但你不需要。只需使用java -p 将字节码转换为可读形式,然后在JVM规范中查找字节码指令的含义即可。

      如果你在这种情况下这样做(就像@Joachim 所做的那样),你会看到字节码中没有实际转换为byte。反编译器弄错了……但这应该不足为奇。

      我对这个序列逻辑的理解

       0: bipush        127
       2: istore_0
       3: iload_0
       4: istore_1
       5: getstatic     #2  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: ldc           #5  // String I am finally
      10: invokevirtual #6  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: iload_1
      14: ireturn
      

      是编译器在 4: 发出指令,将返回表达式保存在临时变量中,以便它可以“内联”finally 块中的代码。在 13:它重新加载值并返回它。

      但是istore_1iload_1 指令正在恢复然后加载int 值。反编译器只是糊涂了。

      【讨论】:

        【解决方案3】:

        127 的值可以存储在一个字节内,因此它试图节省内存。

        【讨论】:

        • 但是如果没有“try”,就不会编译成“byte”
        • 真的吗?那很有意思。据我了解,这两种情况下都应该编译成byte...
        • 没有“尝试”来节省内存。字节码序列的作用是明确的,但在如何反编译方面却是模棱两可的。当字节码中存在与 try/catch 相关的结构时,此处使用的特定反编译器恰好产生了这种解释。
        猜你喜欢
        • 2011-06-25
        • 2010-10-30
        • 1970-01-01
        • 1970-01-01
        • 2016-06-22
        • 2015-11-07
        • 2010-09-06
        • 2020-10-31
        相关资源
        最近更新 更多