为什么javac在类上使用postfix“++”运算符创建一个未使用的变量?

时间:2015-08-28 14:48:48

标签: java compiler-optimization

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

}

3 个答案:

答案 0 :(得分:1)

前缀++的int版本的语义是:++i更简单:i = i + 1; return i

i++意味着更复杂的事情:int i$ = i; i = i + 1; return i$

当int包装在Integer中时,使用的编译器显然不会生成足够智能的代​​码。它实际上保留了ii$以上)的旧值。没有理由那么愚蠢,因为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)

我怀疑这两个临时变量localInteger1localInteger2是为了处理一般装箱/拆箱规则而引入的占位符。不看反编译的字节代码,这可能会揭示一些不在重新解释编译的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可以简单地完成,但处理对象引用更新包括更复杂的情况,因此需要两个临时值。