que - java 8 lambdas pdf
Expresión Lambda y método sobrecargando dudas (3)
Creo que encontraste este error en el compilador: 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 bloqueo 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.
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<Integer> consumer) {
System.out.println("consumer");
}
static void run(Function<Integer, Integer> function) {
System.out.println("function");
}
En Java 7, podría llamarlos fácilmente con clases anónimas no ambiguas como argumentos:
run(new Consumer<Integer>() {
public void accept(Integer integer) {}
});
run(new Function<Integer, Integer>() {
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 compilador debería poder inferir Integer
, ¿por qué no dejo Integer
, entonces?
// Consumer
run(i -> {});
// Function
run(i -> 1);
Pero esto no compila. El compilador (javac, jdk1.8.0_05) no le gusta eso:
Test.java:63: error: reference to run is ambiguous
run(i -> {});
^
both method run(Consumer<Integer>) in Test and
method run(Function<Integer,Integer>) 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 objetivo.
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 here y here .
Pregunta:
¿Quién puede decirme exactamente qué partes de JLS especifican esta ambigüedad en tiempo de compilación (o es un error del compilador)?
Bono: ¿Por qué las cosas se decidieron de esta manera?
Actualizar:
Con jdk1.8.0_40, lo anterior compila y funciona bien
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 declaración 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 declaración 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 compilación para los casos en que el cuerpo no es nulo ni es compatible con el valor, por ejemplo, si compilamos 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 compilador 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<Integer, Integer> f)
run(i->i)
¿Qué métodos podemos agregar legalmente?
void run(BiFunction<Integer, Integer, Integer> f)
void run(Supplier<Integer> 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<Integer, String> f)
Esto da un error de compilació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 compilar. Por lo tanto, el compilador 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 compilar 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<Y,String> f)
void run(Consumer<X> f)
run(i->i.thing())
Aquí esto no se puede compilar, 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 compilar.
En tu ejemplo:
void run(Consumer<Integer> f)
void run(Function<Integer,Integer> 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 compilador no intenta hacer esto. Esta es la primera vez que el compilador 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. .