functional - java lambda predefined interfaces
La referencia del método es ambigua para Thread.sleep (3)
Me encontré con un problema extraño en el que una referencia de método a Thread::sleep
es ambigua, pero un método con la misma firma no lo es.
package test;
public class Test
{
public static void main(String[] args)
{
foo(Test::sleep, 1000L); //fine
foo((FooVoid<Long>)Thread::sleep, 1000L); //fine
foo(Thread::sleep, 1000L); //error
}
public static void sleep(long millis) throws InterruptedException
{
Thread.sleep(millis);
}
public static <P, R> void foo(Foo<P, R> function, P param) {}
public static <P> void foo(FooVoid<P> function, P param) {}
@FunctionalInterface
public interface Foo<P, R> {
R call(P param1) throws Exception;
}
@FunctionalInterface
public interface FooVoid<P> {
void call(P param1) throws Exception;
}
}
Obtengo esos 2 errores:
Error:(9, 17) java: reference to foo is ambiguous
both method <P,R>foo(test.Test.Foo<P,R>,P) in test.Test and method <P>foo(test.Test.FooVoid<P>,P) in test.Test match
Error:(9, 20) java: incompatible types: cannot infer type-variable(s) P,R
(argument mismatch; bad return type in method reference
void cannot be converted to R)
La única diferencia que veo es que Thread::sleep
es native
. ¿Cambia algo? No creo que la sobrecarga Thread::sleep(long, int)
entre en juego aquí. ¿Por que sucede?
EDITAR: Usando la versión 1.8.0_111 de javac
Puede volver a crear el problema en su propia clase agregando un método sleep
con dos argumentos a la clase Test como se muestra a continuación:
public static void sleep(long millis) {
}
public static void sleep(long millis, int nanos) {
}
Entonces, el problema es realmente causado por el hecho de que el método sleep está sobrecargado.
El JLS indica que el código de selección de método inicial solo mira el número de argumentos de tipo a la interfaz funcional, solo en la segunda fase observa la firma del método dentro de la interfaz funcional.
JLS 15.13:
No es posible especificar una firma particular para que coincida, por ejemplo, Arrays :: sort (int []). En cambio, la interfaz funcional proporciona tipos de argumentos que se utilizan como entrada para el algoritmo de resolución de sobrecarga (§15.12.2).
(el penúltimo párrafo de esta sección)
Por lo tanto, en el caso de Thread::sleep
, void sleep(long)
puede coincidir con la interfaz funcional FooVoid<P>
, mientras que FooVoid<P>
void sleep(long, int)
puede coincidir con la interfaz funcional Foo<P, R>
. Es por eso que obtienes el error "la referencia a foo es ambigua".
Cuando intenta ir más allá y ver cómo hacer coincidir Foo<P, R>
con el método funcional R call(P param1)
con el método void sleep(long, int)
, descubre que esto no es realmente posible, y obtienes otro error de compilación:
test/Test.java:7: error: incompatible types: cannot infer type-variable(s) P,R
foo(Thread::sleep, 1000L); // error
^
(argument mismatch; bad return type in method reference
void cannot be converted to R)
Personalmente veo esto como una especie de recursión, de alguna manera como esta: tenemos que resolver el método para encontrar el tipo de destino, pero necesitamos saber el tipo de destino para resolver el método . Esto tiene algo que ver con una regla especial de compatibilidad de vacíos , pero admito que no la entiendo del todo.
Las cosas son aún más divertidas cuando tienes algo como esto:
public static void cool(Predicate<String> predicate) {
}
public static void cool(Function<String, Integer> function) {
}
Y trata de llamarlo a través de:
cool(i -> "Test"); // this will fail compilation
Y por cierto si haces tu lambda explícita , esto funcionará:
foo((Long t) -> Thread.sleep(t), 1000L);
El problema es que ambos, Thread.sleep
y foo
, están sobrecargados. Entonces hay una dependencia circular.
- Para saber qué método de
sleep
usar, necesitamos saber el tipo de objetivo, es decir, qué método de invocación defoo
- Para saber qué método de
foo
invocar, necesitamos conocer la firma funcional del argumento, es decir, qué método desleep
hemos seleccionado
Si bien para un lector humano es claro que para este escenario solo una de las combinaciones de 2 × 2 es válida, el compilador debe seguir reglas formales que funcionen para combinaciones arbitrarias, por lo tanto, los diseñadores del lenguaje tuvieron que hacer un corte.
En aras de la utilidad de las referencias a métodos, existe un tratamiento especial para referencias inequívocas, como su Test::sleep
:
Para algunas expresiones de referencia de método, solo hay una posible declaración en tiempo de compilación con un solo tipo de invocación posible (§15.12.2.6), independientemente del tipo de función objetivo. Dichas expresiones de referencia de método se dice que son exactas . Se dice que una expresión de referencia de método que no es exacta es inexacta .
Tenga en cuenta que esta distinción es similar a la distinción entre expresiones lambda implícitamente tipadas (expresión arg -> expression
) y expresiones lambda tipadas explícitamente ( (Type arg) -> expression
).
Cuando mira JLS, §15.12.2.5., Escogiendo el método más específico , verá que la firma de una referencia de método solo se usa para referencias de método exactas , como cuando se elige el foo
correcto, la decisión para el sleep
correcto método no ha hecho aún.
Si
e
es una expresión de referencia de método exacta (§15.13.1), entonces i) para todo i (1 ≤ i ≤ k),U
i es lo mismo queV
i , y ii) uno de los siguientes es verdadero:
R₂
esvoid
.R₁ <: R₂
.R₁
es un tipo primitivo,R₂
es un tipo de referencia, y la declaración en tiempo de compilación para la referencia del método tiene un tipo de retorno que es un tipo primitivo.R₁
es un tipo de referencia,R₂
es un tipo primitivo, y la declaración en tiempo de compilación para la referencia del método tiene un tipo de retorno que es un tipo de referencia.
La regla anterior se ha indicado en §15.12.2.5. para métodos no genéricos, redirigir a §18.5.4 para métodos genéricos (que se aplica aquí ya que sus métodos foo
son genéricos), que contiene exactamente la misma regla con una redacción ligeramente diferente.
Como la firma de la referencia del método no se tiene en cuenta al elegir el método más específico, no existe un método más específico y la invocación de foo
es ambigua. El segundo error del compilador es el resultado de la estrategia de continuar procesando el código fuente y posiblemente informar más errores, en lugar de detener la compilación en el primer error. Una de las dos invocaciones de foo
provocó un error de "tipos incompatibles", si esa invocación estaba ocurriendo, pero en realidad eso se ha descartado debido al error de "invocación ambigua".