util predefined interfaces functional example java java-8 functional-interface

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 de foo
  • Para saber qué método de foo invocar, necesitamos conocer la firma funcional del argumento, es decir, qué método de sleep 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 :

JLS §15.13.1

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 que V i , y ii) uno de los siguientes es verdadero:

  • R₂ es void .
  • 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".