Expresión Lambda y método sobrecargando dudas

OK, entonces la sobrecarga de métodos es-una-mala-cosa ™. Ahora que esto se ha solucionado, supongamos que realmente quiero sobrecargar un método como este:

static void run(Consumer consumer) { System.out.println("consumer"); } static void run(Function function) { System.out.println("function"); } 

En Java 7, podría llamarlos fácilmente con clases anónimas no ambiguas como argumentos:

 run(new Consumer() { public void accept(Integer integer) {} }); run(new Function() { public Integer apply(Integer o) { return 1; } }); 

Ahora en Java 8, me gustaría llamar a esos métodos con expresiones lambda, por supuesto, ¡y puedo!

 // Consumer run((Integer i) -> {}); // Function run((Integer i) -> 1); 

Como el comstackdor debería poder inferir Integer , ¿por qué no dejo Integer , entonces?

 // Consumer run(i -> {}); // Function run(i -> 1); 

Pero esto no comstack. El comstackdor (javac, jdk1.8.0_05) no le gusta eso:

 Test.java:63: error: reference to run is ambiguous run(i -> {}); ^ both method run(Consumer) in Test and method run(Function) in Test match 

Para mí, intuitivamente, esto no tiene sentido. No hay absolutamente ninguna ambigüedad entre una expresión lambda que arroje un valor de retorno (“valor compatible”) y una expresión lambda que arroje void (“void-compatible”), tal como se establece en JLS §15.27 .

Pero, por supuesto, el JLS es profundo y complejo y heredamos 20 años de historial de compatibilidad con versiones anteriores, y hay cosas nuevas como:

Ciertas expresiones de argumentos que contienen expresiones lambda implícitamente tipadas ( §15.27.1 ) o referencias de métodos inexactos ( §15.13.1 ) son ignoradas por las pruebas de aplicabilidad, porque su significado no puede determinarse hasta que se seleccione un tipo de objective.

de JLS §15.12.2

La limitación anterior probablemente esté relacionada con el hecho de que el JEP 101 no se implementó en su totalidad, como se puede ver aquí y aquí .

Pregunta:

¿Quién puede decirme exactamente qué partes de JLS especifican esta ambigüedad en tiempo de comstackción (o es un error del comstackdor)?

Bono: ¿Por qué las cosas se decidieron de esta manera?

Actualizar:

Con jdk1.8.0_40, lo anterior comstack y funciona bien

Creo que encontraste este error en el comstackdor: JDK-8029718 ( o este similar en Eclipse: 434642 ).

Compare con JLS §15.12.2.1. Identificar métodos potencialmente aplicables :

  • Una expresión lambda (§15.27) es potencialmente compatible con un tipo de interfaz funcional (§9.8) si se cumplen todas las condiciones siguientes:

    • La aridad del tipo de función del tipo de destino es la misma que la aridad de la expresión lambda.

    • Si el tipo de función del tipo de destino tiene un retorno nulo, entonces el cuerpo lambda es una expresión de enunciado (§14.8) o un bloque compatible con el vacío (§15.27.2).

    • Si el tipo de función del tipo de destino tiene un tipo de retorno (no válido), el cuerpo lambda es una expresión o un bloque compatible con el valor (§15.27.2).

Tenga en cuenta la clara distinción entre “bloques compatibles con void ” y “bloques compatibles con el valor”. Mientras que un locking puede ser ambos en ciertos casos, la sección §15.27.2. Lambda Body establece claramente que una expresión como () -> {} es un “bloque compatible con void “, ya que se completa normalmente sin devolver un valor. Y debería ser obvio que i -> {} es un “bloque compatible con void “.

Y de acuerdo con la sección citada anteriormente, la combinación de una lambda con un bloque que no es compatible con el valor y un tipo de destino con un tipo de retorno (no void ) no es un candidato potencial para la resolución de sobrecarga del método. Entonces tu intuición es correcta, no debería haber ambigüedad aquí.

Ejemplos de bloques ambiguos son

 () -> { throw new RuntimeException(); } () -> { while (true); } 

ya que no se completan normalmente, pero este no es el caso en su pregunta.

Este error ya ha sido reportado en el JDK Bug System: https://bugs.openjdk.java.net/browse/JDK-8029718 . Como puedes comprobar, el error ha sido reparado. Esta corrección sincroniza javac con la especificación en este aspecto. En este momento javac acepta correctamente la versión con lambdas implícitos. Para obtener esta actualización, debe clonar javac 8 repo .

Lo que hace la solución es analizar el cuerpo lambda y determinar si es vacío o compatible con el valor. Para determinar esto, debe analizar todas las declaraciones de devolución. Recordemos eso de la especificación (15.27.2), ya mencionada anteriormente:

  • Un bloque de cuerpo lambda es compatible con el vacío si cada statement de retorno en el bloque tiene el retorno del formulario.
  • Un bloque lambda body es compatible con el valor si no se puede completar normalmente ( 14.21 ) y cada statement de retorno en el bloque tiene la forma return Expression.

Esto significa que al analizar los rendimientos en el cuerpo lambda puede saber si el cuerpo lambda es compatible con el vacío, pero para determinar si es compatible con el valor, también debe hacer un análisis de flujo para determinar que se puede completar normalmente ( 14.21 ).

Esta solución también introduce un nuevo error de comstackción para los casos en que el cuerpo no es nulo ni es compatible con el valor, por ejemplo, si comstackmos este código:

 class Test { interface I { String f(String x); } static void foo(I i) {} void m() { foo((x) -> { if (x == null) { return; } else { return x; } }); } } 

el comstackdor dará esta salida:

 Test.java:9: error: lambda body is neither value nor void compatible foo((x) -> { ^ Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output 1 error 

Espero que esto ayude.

Supongamos que tenemos método y método de llamada

 void run(Function f) run(i->i) 

¿Qué métodos podemos agregar legalmente?

 void run(BiFunction f) void run(Supplier f) 

Aquí el parámetro arity es diferente, específicamente la parte i-> de i->i no se ajusta a los parámetros de apply(T,U) en BiFunction , o get() en Supplier . De modo que aquí cualquier ambigüedad posible se define por el parámetro arity, no por tipos, y no por el retorno.


¿Qué métodos no podemos agregar?

 void run(Function f) 

Esto da un error de comstackción como run(..) and run(..) have the same erasure . Entonces, como la JVM no puede admitir dos funciones con el mismo nombre y tipo de argumentos, no se puede comstackr. Por lo tanto, el comstackdor nunca tiene que resolver las ambigüedades en este tipo de escenario, ya que están explícitamente desautorizadas debido a las reglas preexistentes en el sistema de tipo Java.

Entonces eso nos deja con otros tipos funcionales con un parámetro arity de 1.

 void run(IntUnaryOperator f) 

Aquí run(i->i) es válido tanto para Function como para IntUnaryOperator , pero esto se negará a comstackr debido a que la reference to run is ambiguous ya que ambas funciones coinciden con esta lambda. De hecho lo hacen, y es de esperar un error aquí.

 interface X { void thing();} interface Y { String thing();} void run(Function f) void run(Consumer f) run(i->i.thing()) 

Aquí esto no se puede comstackr, nuevamente debido a ambigüedades. Sin saber el tipo de i en esta lambda es imposible saber el tipo de i.thing() . Por lo tanto, aceptamos que esto es ambiguo y con razón no se puede comstackr.


En tu ejemplo:

 void run(Consumer f) void run(Function f) run(i->i) 

Aquí sabemos que ambos tipos funcionales tienen un único parámetro Integer , por lo que sabemos que i en i-> debe ser un Integer . Entonces sabemos que se debe run(Function) que se llama. Pero el comstackdor no intenta hacer esto. Esta es la primera vez que el comstackdor hace algo que no esperamos.

¿Por qué no hace esto? Diría que es un caso muy específico, e inferir el tipo aquí requiere mecanismos que no hemos visto para ninguno de los otros casos anteriores, porque en el caso general no pueden inferir correctamente el tipo y elegir el método correcto. .