为什么try里的int会编译成byte

Why is the int in try compiled into byte

我发现一个现象:

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"

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

如果你想看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 块时要 returned 的值 (8-10)。然后我们使用 iload_1 将其加载回来并 return 它。

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

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

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 处发出了指令:将 return 表达式保存在一个临时变量中,以便它可以“内联” finally 块中的代码。在 13:它重新加载值并 returns 它。

但是 istore_1iload_1 指令正在恢复然后加载 int 值。反编译器很困惑。