Lambda behaving differently than anonymous inner class
Asked Answered
I

1

25

While doing some basic lambda exercises, the output from an apparently identical anonymous inner class was giving me a different output than the lambda.

interface Supplier<T> {

    T get(T t);
}

Scenario #1

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return t;
    }
};
Supplier<Integer> s2 = t -> t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

Outputs 2 and 2. Nothing new here.


But when I do this:

Scenario #2

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return t++;
    }
};
Supplier<Integer> s2 = t -> t++;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

Outputs 2 and 3

QUESTION: Shouldn't both outputs be identical? Am I missing something?


For the sake of completeness: Scenario #3

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return ++t;
    }
};
Supplier<Integer> s2 = t -> ++t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

Outputs 3 and 3. Nothing new here as well.

UPDATE: Still getting same output from 1.8.0-b132

UPDATE #2: Bug report: https://bugs.openjdk.java.net/browse/JDK-8038420

UPDATE #3: The bug has been fixed in javac, you should be able to obtain the same result now.

Intravasation answered 25/3, 2014 at 22:57 Comment(9)
I get 2 and 2 for your second snippet.Curtain
+1 for cannot replicateKutch
Well, that's interesting. Are we looking at something related to JVM versions/builds here? I'm using build 1.8.0-ea-b81. Currently downloading a newer version. Will post updates.Intravasation
I reproduce the bug on MacOS X Mavericks. Output of java -version: java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)Lott
I can reproduce it with Java(TM) SE Runtime Environment (build 1.8.0-ea-b118), but not on whatever JRE8 Eclipse is using.Curtain
I can reproduce it as well. java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)Canzonet
Reproduced as well: Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)Flagstad
When running under Eclipse, you are using Eclipse's compiler which doesn't have this bug -- it's not a VM problem, it's a compiler thing.Cholesterol
interesting: replace it to interface Supplier {int get(int t);} and it will work as expectedAla
C
14

According to generated bytecode:

Java(TM) SE Runtime Environment (build 1.8.0-b132)

Lambda:

 private static java.lang.Integer lambda$main$0(java.lang.Integer);
   descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
   flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   Code:
     stack=2, locals=2, args_size=1
        0: aload_0
        1: invokevirtual #9                  // Method java/lang/Integer.intValue:()I
        4: iconst_1
        5: iadd
        6: invokestatic  #6                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        9: dup
       10: astore_0
       11: astore_1
       12: aload_0
       13: areturn
     LineNumberTable:
       line 20: 0
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      14     0     t   Ljava/lang/Integer;

Anonymous class:

  public java.lang.Integer get(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_1
         1: astore_2
         2: aload_1
         3: invokevirtual #2                  // Method java/lang/Integer.intValue:()I
         6: iconst_1
         7: iadd
         8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: dup
        12: astore_1
        13: astore_3
        14: aload_2
        15: areturn
      LineNumberTable:
        line 16: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   LTest$1;
            0      16     1     t   Ljava/lang/Integer;

As you can see, in anonymous class after loading variable from local variable table (method parameter t) runtime store copy of parameter in another variable (astore_2) and then use this copy of parameter as returning value.

Lambda method doesn't make copy of parameter (load -> unbox -> add 1 -> box -> store -> load -> return).

UPDATE

It's definitely a javac bug.

I got source from http://hg.openjdk.java.net/jdk8u/jdk8u

Anonymous class and lambda converts to following intermediate representations:

@Override()
public Integer get(Integer t) {
    return (let /*synthetic*/ final Integer $112619572 = t in 
       (let /*synthetic*/ final Integer $1295226194 = t = Integer.valueOf((int)(t.intValue() + 1)) in $112619572));
}

/*synthetic*/ private static Integer lambda$main$0(final Integer t) {
    return (let /*synthetic*/ final Integer $1146147158 = t = Integer.valueOf((int)(t.intValue() + 1)) in t);
}

In lambda generated method parameter marked as final, because LambdaToMethod translator marks all parameters as FINAL (according source code LambdaTranslationContext.translate(…):1899).

Then let expression builder checks variable flags and when if it’s final omits temporary variable generation (according source code Lower.abstractRval(…):2277), because modification considered to be prohibited.

Possible solutions:

  1. Forbid parameter modification inside lambda or
  2. Remove FINAL flag from local variable (LambdaTranslationContext.translate(…):1894) and parameter (LambdaTranslationContext.translate(…):1899) in lamda generated method:

     case LOCAL_VAR:
       ret = new VarSymbol(FINAL, name, types.erasure(sym.type), translatedSym);
     ...
    
     case PARAM:
       ret = new VarSymbol(FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym);
     ...
    

I removed FINAL flag and got expected results on tests from: https://bugs.openjdk.java.net/browse/JDK-8038420

Carnival answered 26/3, 2014 at 8:53 Comment(5)
Even though there's no apparent official word on the matter yet, I'm accepting this as the correct answer. Guess I should file a bug report. Can anyone point me out on how to do that?Intravasation
mail.openjdk.java.net/pipermail/lambda-dev - OpenJDK lambda-dev mailing listCarnival
Yes, that looks like a bug in javac. Fortunately, Eclipse's Java compiler doesn't have that bug.Cholesterol
I've posted this question on the lambda-dev list: mail.openjdk.java.net/pipermail/lambda-dev/2014-March/…Intravasation
I've updated my answer with copy of my message from here: mail.openjdk.java.net/pipermail/lambda-dev/2014-March/…Carnival

© 2022 - 2024 — McMap. All rights reserved.