受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类时会发生什么,这次使用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 ++”做了一些愚蠢的事情。它创建了两个新的整数类,并创建了一个逻辑上不必要的一系列赋值:
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)
=================== BY REQUEST:javap输出=======================
使用Integer
类进行后缀增量。
{
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
}
答案 0 :(得分:1)
前缀++的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);
答案 1 :(得分:1)
正如您在类的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。
答案 2 :(得分:0)
我怀疑这两个临时变量localInteger1
和localInteger2
是为了处理一般装箱/拆箱规则而引入的占位符。不看反编译的字节代码,这可能会揭示一些不在重新解释编译的java代码中的细节,而且没有看其他一些不那么微不足道的情况,我只是在推测。
但是,显而易见的是,它们在循环i
表达式之前和之后都保留ForUpdate
的值。
考虑这种情况:
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
可以简单地完成,但处理对象引用更新包括更复杂的情况,因此需要两个临时值。