为什么 javac 在 class 上使用后缀“++”运算符创建一个未使用的变量?
Why is javac creating an unused variable with the postfix "++" operator on a class?
受到 this question 的启发,我开始了一些研究。
我能够确定使用基元,前缀 ++i
只是被编译器重写为 i++
:
之前:
public class PrefixIncrement {
public static void main(String args[]) {
for(Integer i = 0; i < 100; ++i) {
System.out.println(i);
}
}
}
用jd-gui 0.36反编译:
import java.io.PrintStream;
public class PrefixIncrement
{
public static void main(String[] args)
{
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
好的。所以那部分得到了回答。但是后来我偶然发现了当我们使用 Integer class 时会发生什么,这次是使用 Postfix:
之前:
public class PostfixIncrement {
public static void main(String args[]) {
for( Integer i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
反编译后:
import java.io.PrintStream;
public class PostfixIncrement
{
public static void main(String[] args)
{
Integer localInteger1;
Integer localInteger2;
for (Integer i = Integer.valueOf(0); i.intValue() < 100; localInteger2 = i = Integer.valueOf(i.intValue() + 1))
{
System.out.println(i);localInteger1 = i;
}
}
}
编译器似乎用“i++”做了一些愚蠢的事情。它创建了两个新的整数 classes 并创建了一系列逻辑上不必要的赋值:
localInteger2 = i = Integer.valueOf(i.intValue() + 1);
localInteger1
似乎从未被使用过。它只是分配在堆栈上。为什么 javac 这样做?
$ java -version
java version "1.7.0_60"
Java(TM) SE Runtime Environment (build 1.7.0_60-b19)
Java HotSpot(TM) 64-Bit Server VM (build 24.60-b09, mixed mode)
===================根据请求:javap 输出===================== ==
后缀增量使用 Integer
class.
{
public com.foo.PostfixIncrement();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>
":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/matt/PostfixIncrement;
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_0
1: invokestatic #2 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3 // Method java/lang/Integer.intVal
ue:()I
9: bipush 100
11: if_icmpge 40
14: getstatic #4 // Field java/lang/System.out:Ljav
a/io/PrintStream;
17: aload_1
18: invokevirtual #5 // Method java/io/PrintStream.prin
tln:(Ljava/lang/Object;)V
21: aload_1
22: astore_2
23: aload_1
24: invokevirtual #3 // Method java/lang/Integer.intVal
ue:()I
27: iconst_1
28: iadd
29: invokestatic #2 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
32: dup
33: astore_1
34: astore_3
35: aload_2
36: pop
37: goto 5
40: return
LineNumberTable:
line 5: 0
line 6: 14
line 5: 21
line 8: 40
LocalVariableTable:
Start Length Slot Name Signature
5 35 1 i Ljava/lang/Integer;
0 41 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 5
locals = [ class java/lang/Integer ]
frame_type = 250 /* chop */
offset_delta = 34
}
前缀++的int版本的语义是:++i
更简单:i = i + 1; return i
.
而 i++
意味着更复杂的事情:int i$ = i; i = i + 1; return i$
.
当 int 包装在 Integer 中时,使用的编译器显然不会生成足够智能的代码。它实际上保持了 i
的旧值(上面的 i$
)。没有理由那么愚蠢,因为 i++
的结果没有在那个位置使用。
for (...; ...; (void)[[ i++ ]] ) {
在第一个实例中生成
Integer i$ = i;
i = Integer.valueOf(i.intValue() + 1);
"return" i$ to a void context
drop variable i$ where i$ is unused
应该很容易减少到:
i = Integer.valueOf(i.intValue() + 1);
我怀疑这两个临时变量 localInteger1
和 localInteger2
是为处理一般 boxing/unboxing 规则而引入的占位符。在不查看反编译字节码的情况下,这可能会揭示一些在重新解释编译后的 java 代码中没有的细节,并且在不查看其他一些不那么微不足道的情况的情况下,我只是在推测。
然而,很明显它们在循环 ForUpdate
表达式之前和之后都保留了 i
的值。
考虑这个案例:
Integer i;
for( i = 0; i < 100; i++) {
System.out.println(i);
}
System.out.println(i); // what is the value of `i` here?
这在 Java 代码中与基元的情况相同,除了声明的类型是 Integer
而不是 int
。
编译器必须在循环执行期间和之后跟踪 i
的值,此时它将是 100
。我怀疑在某些情况下实现循环和处理原语 int
可以轻松完成,但处理对象引用更新包括更复杂的情况,因此需要两个临时值。
正如您在 class 的 javap
输出中看到的(使用 javac
1.8.0_60 编译),变量实际上并不存在:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3 // Method java/lang/Integer.intValue:()I
9: bipush 100
11: if_icmpge 34
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
21: aload_1
22: invokevirtual #3 // Method java/lang/Integer.intValue:()I
25: iconst_1
26: iadd
27: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
30: astore_1
31: goto 5
34: return
(局部变量与*LOAD
和*STORE
指令一起使用,如您所见,只涉及一个。)
问题似乎是由反编译器引起的,它可能不太支持 for
语句中的装箱。所以 javac
没有做错,只是反编译器的错。无需担心性能影响或任何事情,除非您计划进行逆向工程并保存反编译代码:P.
受到 this question 的启发,我开始了一些研究。
我能够确定使用基元,前缀 ++i
只是被编译器重写为 i++
:
之前:
public class PrefixIncrement {
public static void main(String args[]) {
for(Integer i = 0; i < 100; ++i) {
System.out.println(i);
}
}
}
用jd-gui 0.36反编译:
import java.io.PrintStream;
public class PrefixIncrement
{
public static void main(String[] args)
{
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
好的。所以那部分得到了回答。但是后来我偶然发现了当我们使用 Integer class 时会发生什么,这次是使用 Postfix:
之前:
public class PostfixIncrement {
public static void main(String args[]) {
for( Integer i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
反编译后:
import java.io.PrintStream;
public class PostfixIncrement
{
public static void main(String[] args)
{
Integer localInteger1;
Integer localInteger2;
for (Integer i = Integer.valueOf(0); i.intValue() < 100; localInteger2 = i = Integer.valueOf(i.intValue() + 1))
{
System.out.println(i);localInteger1 = i;
}
}
}
编译器似乎用“i++”做了一些愚蠢的事情。它创建了两个新的整数 classes 并创建了一系列逻辑上不必要的赋值:
localInteger2 = i = Integer.valueOf(i.intValue() + 1);
localInteger1
似乎从未被使用过。它只是分配在堆栈上。为什么 javac 这样做?
$ java -version
java version "1.7.0_60"
Java(TM) SE Runtime Environment (build 1.7.0_60-b19)
Java HotSpot(TM) 64-Bit Server VM (build 24.60-b09, mixed mode)
===================根据请求:javap 输出===================== ==
后缀增量使用 Integer
class.
{
public com.foo.PostfixIncrement();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>
":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/matt/PostfixIncrement;
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_0
1: invokestatic #2 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3 // Method java/lang/Integer.intVal
ue:()I
9: bipush 100
11: if_icmpge 40
14: getstatic #4 // Field java/lang/System.out:Ljav
a/io/PrintStream;
17: aload_1
18: invokevirtual #5 // Method java/io/PrintStream.prin
tln:(Ljava/lang/Object;)V
21: aload_1
22: astore_2
23: aload_1
24: invokevirtual #3 // Method java/lang/Integer.intVal
ue:()I
27: iconst_1
28: iadd
29: invokestatic #2 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
32: dup
33: astore_1
34: astore_3
35: aload_2
36: pop
37: goto 5
40: return
LineNumberTable:
line 5: 0
line 6: 14
line 5: 21
line 8: 40
LocalVariableTable:
Start Length Slot Name Signature
5 35 1 i Ljava/lang/Integer;
0 41 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 5
locals = [ class java/lang/Integer ]
frame_type = 250 /* chop */
offset_delta = 34
}
前缀++的int版本的语义是:++i
更简单:i = i + 1; return i
.
而 i++
意味着更复杂的事情:int i$ = i; i = i + 1; return i$
.
当 int 包装在 Integer 中时,使用的编译器显然不会生成足够智能的代码。它实际上保持了 i
的旧值(上面的 i$
)。没有理由那么愚蠢,因为 i++
的结果没有在那个位置使用。
for (...; ...; (void)[[ i++ ]] ) {
在第一个实例中生成
Integer i$ = i;
i = Integer.valueOf(i.intValue() + 1);
"return" i$ to a void context
drop variable i$ where i$ is unused
应该很容易减少到:
i = Integer.valueOf(i.intValue() + 1);
我怀疑这两个临时变量 localInteger1
和 localInteger2
是为处理一般 boxing/unboxing 规则而引入的占位符。在不查看反编译字节码的情况下,这可能会揭示一些在重新解释编译后的 java 代码中没有的细节,并且在不查看其他一些不那么微不足道的情况的情况下,我只是在推测。
然而,很明显它们在循环 ForUpdate
表达式之前和之后都保留了 i
的值。
考虑这个案例:
Integer i;
for( i = 0; i < 100; i++) {
System.out.println(i);
}
System.out.println(i); // what is the value of `i` here?
这在 Java 代码中与基元的情况相同,除了声明的类型是 Integer
而不是 int
。
编译器必须在循环执行期间和之后跟踪 i
的值,此时它将是 100
。我怀疑在某些情况下实现循环和处理原语 int
可以轻松完成,但处理对象引用更新包括更复杂的情况,因此需要两个临时值。
正如您在 class 的 javap
输出中看到的(使用 javac
1.8.0_60 编译),变量实际上并不存在:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3 // Method java/lang/Integer.intValue:()I
9: bipush 100
11: if_icmpge 34
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
21: aload_1
22: invokevirtual #3 // Method java/lang/Integer.intValue:()I
25: iconst_1
26: iadd
27: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
30: astore_1
31: goto 5
34: return
(局部变量与*LOAD
和*STORE
指令一起使用,如您所见,只涉及一个。)
问题似乎是由反编译器引起的,它可能不太支持 for
语句中的装箱。所以 javac
没有做错,只是反编译器的错。无需担心性能影响或任何事情,除非您计划进行逆向工程并保存反编译代码:P.