英文:
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_1
和iload_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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论