2

Inspired by this question I started out on a little research.

I was able to determine that using primitives, the prefix ++i just gets rewritten to i++ by the compiler:

Before:

public class PrefixIncrement {
    public static void main(String args[]) {
        for(Integer i = 0; i < 100; ++i) {
            System.out.println(i);
        }   
    }
}

Decompiled with 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);
    }
  }
}

Okay. So that part was answered. But then I stumbled across what happens when we use the Integer class instead, this time with Postfix:

Before:

public class PostfixIncrement {
    public static void main(String args[]) {
        for( Integer i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

After decompilation:

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

The compiler seems to do some stupid stuff with “i++.” It creates two new integer classes and creates a logically unnecessary series of assignments:

localInteger2 = i = Integer.valueOf(i.intValue() + 1);

localInteger1 doesn’t appear to ever be used. Its just allocated on the stack. Why is javac doing this?

$ 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 output=======================

Postfix Increment using the 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

}
Community
  • 1
  • 1
avgvstvs
  • 6,196
  • 6
  • 43
  • 74
  • 2
    What was the original code with the `Integer` class? That's the one that matters and you don't show here. Did you look at the generated bytecode? Decompilation is not necessarily an accurate representation of the actual source code. – Jeroen Vannevel Aug 28 '15 at 14:55
  • @JeroenVannevel --> I updated with the "Integer" version. The short version is just to replace "int" with "Integer" in the for loop declaration. – avgvstvs Aug 28 '15 at 15:16
  • 1
    Don't rely on autoboxing for arithmetic operations. Use built-in types instead (`int`). Relying on autoboxing will lead to hidden performance issues like the one you're seeing. – rustyx Aug 28 '15 at 16:01
  • You should also post the `javap` output. Sometimes decompilers insert crazy local variables that are not really there when they encounter stack operations like `DUP`, whose semantics are not expressible in Java. – Clashsoft Aug 28 '15 at 16:48
  • You rely on the decompiler output to deduce that for the int case ++i is becoming i++; thats *not* whats *really* happening - when javac compiles either, it will emit multiple byte codes; some related to handling the value of the expression and a separate (iinc) to do the actual increment. Its the order of byte codes that determines if its a pre- or post-increment. Since the value was not used, the compiler omitted the value handling byte codes, reducing both cases to a simple iinc. The semantic information if it has been a pre- or postincrement is thus no longer present in byte code – Durandal Aug 28 '15 at 17:44
  • @Clashsoft I added javap output for the Postfix using an `Integer` class. – avgvstvs Aug 28 '15 at 17:55
  • @rustyx -- Currently, the code under question isn't using autoboxing, its a direct instance of the `Integer` class. see `for( Integer i = 0; ...` – avgvstvs Aug 28 '15 at 17:57
  • Of course it does use autoboxing. `Integer` is an immutable object. I'm impossible to 'iterate' it. Attempting `++` will unbox it, increment a built-in type, then autobox the result back. – rustyx Aug 31 '15 at 09:56
  • @rustyx I did some research and stand corrected. – avgvstvs Aug 31 '15 at 19:04

3 Answers3

1

The semantics of the int version of prefix ++ is: ++i is simpler: i = i + 1; return i.

Whereas i++ means a more complicated thing: int i$ = i; i = i + 1; return i$.

When int is wrapped in an Integer the used compiler evidently does not generate sufficiently intelligent code. It actually maintains the old value of i (i$ above). No reason to be that dumb, as the result of i++ is not used at that position.

for (...; ...; (void)[[ i++ ]] ) {

generates in the first instance

Integer i$ = i;
i = Integer.valueOf(i.intValue() + 1);
"return" i$ to a void context
drop variable i$ where i$ is unused

Which should easily have been reduced to:

i = Integer.valueOf(i.intValue() + 1);
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 1
    Actually, I doubt the compiler needs a synthetic local variable to do that operation; a `DUP` should also do the job. And that in turn works just as well with a boxed integer. – Clashsoft Aug 28 '15 at 16:11
  • @Clashsoft local variable and expression stack are just slightly different alternatives. A `DUP` of the Integer object of `i` can be done as Integer is **immutable**. And that duplication would be unneeded too after usage analysis. – Joop Eggen Aug 28 '15 at 16:34
  • I doubt a reasonably smart compiler would know that it doesn't even need one in the context of a `for` statement's update code, since it's expected type is `void` anyway. I just wanted to point out that saying "*`i++` means a more complicated thing: `int i$ = i; i = i + 1; return i$`*" is not technically correct in all cases. It's just that you can't express `DUP` semantics in *Java* code. – Clashsoft Aug 28 '15 at 16:45
  • @Clashsoft `i++` as `dup i; i = i + 1; (pop)` is nicer style indeed. And yes compiler theory can do expression trees resulting in dup and other instructions. Though doubt on intelligence I have too, seeing the complexity of the language constructs. – Joop Eggen Aug 28 '15 at 17:02
  • On an interesting side-note: When using `i++` (`Integer i`) in an expression context, it either prefers `Object` over unboxing (for `sysout`) or does the `DUP` on the reference rather than the unboxed value, thus inserting two `.intValue()` calls – Clashsoft Aug 28 '15 at 17:06
1

As you can see in the javap output of your class (compiled with javac 1.8.0_60), the variables do not actually exist:

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

(Local variables are used with *LOAD and *STORE instructions, and as you can see, there is only a single one involved.)

The problem seems to be caused by decompiler, which probably doesn't support boxing within for statements really well. So javac does nothing wrong, it's just the decompiler's fault. No need to worry about performance impacts or anything, unless you plan on reverse-engineering and saving the decompiled code :P.

Clashsoft
  • 11,553
  • 5
  • 40
  • 79
  • I posted the javap output from the javac version that I'm using. It appears that it must be related to the `DUP` and `STORE` issue as you suggest. – avgvstvs Aug 28 '15 at 17:58
  • It seems like the Java 7 compiler does things a bit differently. I recommend upgrading to Java 8 anyway, since 7 is not supported anymore. – Clashsoft Aug 28 '15 at 22:09
  • Remember that it's possible for an application to require rewriting from JDK version to JDK version. Politics can keep that from being priority. – avgvstvs Aug 28 '15 at 23:13
0

I suspect those two temporary variables, localInteger1 and localInteger2 are placeholders introduced to handle general boxing/unboxing rules. Without looking at the decompiled byte code, which may reveal some details not in the reinterpreted-from-compiled java code, and without looking at some other less trivial cases, I am only speculating.

However, it is obvious though that they are retaining the value of i both before and after the loop ForUpdate expression.

Consider this case:

Integer i;
for( i = 0; i < 100; i++) {
   System.out.println(i);
}
System.out.println(i); // what is the value of `i` here?

Which is identical in Java code to the case of the primitive other than the declared type being Integer instead of int.

The compiler must track the value of i through and after the execution of the loop, at which point it will be 100. I suspect there are some cases in implementing a loop and handling the primitive int can be done trivially but handling object reference updates includes more complex cases and thus requires the two temporary values.

Paul Bilnoski
  • 556
  • 4
  • 13