Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda behaving differently than anonymous inner class

Tags:

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.

like image 269
victorantunes Avatar asked Mar 25 '14 22:03

victorantunes


People also ask

How is lambda different from anonymous class?

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.

How lambda expressions are better than anonymous inner classes?

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.

Can lambda expression be used in place of instances of anonymous classes?

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.


1 Answers

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

like image 170
Ivan Babanin Avatar answered Sep 28 '22 09:09

Ivan Babanin