为什么try中的int被编译成了byte

huangapple go评论74阅读模式
英文:

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 = (byte) 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"。

英文:

I find a phenomenon:

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;
    }
}

compiled to .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;
    }
}

why "int a" convert to "byte var1"?
Is the purpose to save memory?
Isn't this unnecessary?
I want to know how the compiler handles this.
<br />
but I find if code like this:

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

compiled to .class:

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

If there is no "try", it will not be compiled into "byte"

答案1

得分: 4

如果你想查看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
      ...

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

唯一与字节相关的指令是bipush,它将byte值推送到堆栈上,但在堆栈上会被扩展为int值。这只是在sipush(推送short常量)或ldc(它要求将值存储在常量池中)之间节省了几个字节。

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

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

另外,你可能会注意到字节码看起来非常低效,但那只是因为javac编译器将代码直接转换成字节码,并没有进行任何优化。任何实际的优化(比如实际上根本不将任何值存储在任何变量中,而只是返回常量值127)都将由JVM在运行时进行。

英文:

If you want to look at what something is compiled into in Java, you shouldn't look at the decompiled code. Converting a .class file back into a .java file involves a lot of interpretation (one could even say guessing) and should not be taken as an indication what the actual compilation looks like. Look at the javap -v output instead, that will show you the actual bytecode. The bytecode of your method looks like this (I removed some unnecessary detail, run it yourself to check it all):

  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

Nothing in there indicates that anything is stored in a byte (iload_* and istore_* load and store integer values).

The only byte-specific instruction is bipush which pushes a byte value, but it will be extended to an int value on the stack. This simply saves a couple of bytes over sipush (push a short constant) or ldc (which would require the value to be stored in the constant pool).

The istore_1 is used to remember the value to be returned while the finally block is executed (8-10). Then we use iload_1 to load it back out and return it.

The decompiler probably thinks that this is an actual variable, when it's just a synthetic constructed caused by the try-catch-finally construct.

Also, you might notice that the bytecode looks horribly inefficient, but that' simply because the javac compiler does a very straight-forward translation into bytecode and really doesn't do any optimizations. Any actual optimizations (such as never actually storing any values in any variables but simply returning the constant value 127) will be done by the JVM at runtime.

答案2

得分: 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值。反编译器只是混淆了。

英文:

Java decompilers are notorious for producing odd looking, incorrect and even non-compilable Java code from bytecodes. You cannot infer that the Java compiler has compiled a Java source file in a particular way by looking at the decompiled code.

But you don't need to. Just use java -p to convert the bytecodes into a readable form, and then lookup what the bytecode instructions mean in the JVM specification.

If you do it in this case (as @Joachim has done), you will see that there is no actually conversion to a byte in the bytecodes. The decompiler has got it wrong ... but that should not be a big surprise.

My understanding of the logic of this sequence

 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

is that the compiler has emitted the instruction at 4: to save the return expression in a temporary variable so that it can "inline" the code in the finally block. At 13: it reloads the value and returns it.

But the istore_1 and iload_1 instructions are restoring and then loading an int value. The decompiler is just confused.

答案3

得分: -7

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

英文:

The value of 127 can be stored inside a byte, so it's trying to save on memory.

huangapple
  • 本文由 发表于 2020年7月25日 18:59:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/63087507.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定