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.
Anonymous class is an inner class without a name, which means that we can declare and instantiate class at the same time. A lambda expression is a short form for writing an anonymous class. By using a lambda expression, we can declare methods without any name.
Lambda expression can be used where a class implements a functional interface to reduce the complexity of the code. An inner anonymous class is more powerful as we can use many methods as we want, whereas lambda expression can only be used where an interface has only a single abstract method.
By the way, you cannot always use lambda expression in place of Anonymous class, because of its limitation of being SAM type. If you are using an anonymous class to implement an interface with two abstract methods then you cannot replace it with a lambda of Java 8.
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:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With