una otra objeto interfaces instanciar funcion externas ejemplos dentro crear como clases clase anonimas anonima anidada java lambda java-8 anonymous-inner-class

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:

  1. Prohibir modificación de parámetros dentro de lambda o
  2. 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