问题
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.
回答1:
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:
- Forbid parameter modification inside lambda or
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
来源:https://stackoverflow.com/questions/22648079/lambda-behaving-differently-than-anonymous-inner-class