java - otra - Lambda se comporta de manera diferente a la clase interna anónima
interfaces en java ejemplos (1)
Mientras hacía algunos ejercicios básicos de lambda, el resultado de una clase interna anónima aparentemente idéntica me daba un resultado diferente al lambda.
interface Supplier<T> {
T get(T t);
}
Escenario 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));
Salidas 2 y 2 . Nada nuevo aquí.
Pero cuando hago esto:
Escenario # 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));
Salidas 2 y 3
PREGUNTA: ¿No deberían ambas salidas ser idénticas? ¿Me estoy perdiendo de algo?
En aras de la exhaustividad: escenario # 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));
Salidas 3 y 3 . Nada nuevo aquí también.
ACTUALIZACIÓN: sigue obteniendo la misma salida de 1.8.0-b132
ACTUALIZACIÓN # 2: Informe de error: https://bugs.openjdk.java.net/browse/JDK-8038420
ACTUALIZACIÓN # 3: El error ha sido reparado en javac, ahora debería poder obtener el mismo resultado.
De acuerdo con bytecode generado:
Java (TM) SE Runtime Environment (compilación 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;
Clase anónima:
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;
Como puede ver, en la clase anónima después de cargar la variable de la tabla de variables locales (parámetro de método t ), en tiempo de ejecución, guarde la copia del parámetro en otra variable ( astore_2 ) y luego use esta copia del parámetro como valor de retorno.
El método Lambda no hace copia del parámetro (carga -> unbox -> agregar 1 -> caja -> almacenar -> cargar -> devolver).
ACTUALIZAR
Definitivamente es un error de javac.
Obtuve el origen de http://hg.openjdk.java.net/jdk8u/jdk8u
La clase anónima y lambda se convierten en las siguientes representaciones intermedias:
@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);
}
En el parámetro del método lambda generado marcado como final, porque el traductor LambdaToMethod marca todos los parámetros como FINAL (según el código fuente LambdaTranslationContext.translate (...): 1899 ).
Luego, deje que el generador de expresiones compruebe los indicadores de variables y, si es final, omite la generación de variables temporales (según el código fuente Lower.abstractRval (...): 2277 ), ya que la modificación se considera prohibida.
Soluciones posibles:
- Prohibir modificación de parámetros dentro de lambda o
Quite el indicador FINAL de la variable local ( LambdaTranslationContext.translate (...): 1894 ) y el parámetro ( LambdaTranslationContext.translate (...): 1899 ) en el método generado de lamda:
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); ...
Quité el indicador FINAL y obtuve los resultados esperados en las pruebas de: https://bugs.openjdk.java.net/browse/JDK-8038420