En primer lugar, no tengo idea de cómo formular decentemente la pregunta, por lo que se aceptan sugerencias.
Digamos que tenemos los siguientes métodos sobrecargados:
void execute(Callable<Void> callable) { try { callable.call(); } catch (Exception e) { e.printStackTrace(); } } <T> T execute(Supplier<T> supplier) { return supplier.get(); } void execute(Runnable runnable) { runnable.run(); }Al salir de esta mesa, obtuve otra pregunta SO
Supplier () -> x Consumer x -> () BiConsumer x, y -> () Callable () -> x throws ex Runnable () -> () Function x -> y BiFunction x,y -> z Predicate x -> boolean UnaryOperator x1 -> x2 BinaryOperator x1,x2 -> x3Estos son los resultados que obtengo localmente:
// Runnable -> expected as this is a plain void execute(() -> System.out.println()); // Callable -> why is it not a Supplier? It does not throw any exceptions.. execute(() -> null); // Supplier -> this returns an Object, but how is that different from returning null? execute(() -> new Object()); // Callable -> because it can throw an exception, right? execute(() -> {throw new Exception();}); ¿Cómo sabe el compilador a qué método llamar? ¿Cómo hace, por ejemplo, la distinción entre lo que es un Callable y lo que es un Runnable ?
Todo tiene sentido y tiene un patrón simple además de () -> null es un Callable , creo. El Runnable es claramente diferente del Supplier / Callable ya que no tiene valores de entrada y salida. La diferencia entre Callable y Callable Supplier que manejar las excepciones.
La razón por la que () -> null es un Callable sin excepción es el tipo de retorno de su definición Callable<Void> . Requiere que devuelvas la referencia a algún objeto. La única referencia posible para volver a Void es null . Esto significa que lambda () -> null es exactamente lo que exige su definición. También funcionaría para su ejemplo de Supplier si eliminara la definición de Callable . Sin embargo, utiliza Callable<Void> sobre Supplier<T> ya que Callable tiene el tipo exacto.
Callable se elige sobre Supplier ya que es más específico (como ya se sugirió en un comentario). Los documentos de Java afirman que elige el tipo más específico si es posible:
La inferencia de tipo es la capacidad de un compilador de Java para observar cada invocación de método y la declaración correspondiente para determinar el argumento (o argumentos) de tipo que hacen que la invocación sea aplicable. El algoritmo de inferencia determina los tipos de argumentos y, si está disponible, el tipo que se asigna o se devuelve al resultado. Finalmente, el algoritmo de inferencia intenta encontrar el tipo más específico que funcione con todos los argumentos.
Creo que he encontrado dónde se describe esto en la documentación oficial, aunque un poco difícil de leer.
Aquí se menciona:
15.27.3. Tipo de una expresión lambda
Tenga en cuenta que, si bien no se permiten las casillas en un contexto de invocación estricto, las expresiones de resultado lambda siempre están permitidas; es decir, la expresión de resultado aparece en un contexto de asignación, independientemente del contexto que encierra la expresión lambda. Sin embargo, si una expresión lambda tipeada explícitamente es un argumento para un método sobrecargado, la verificación más específica prefiere una firma de método que evite encajonar o desencajar el resultado lambda (§15.12.2.5).
y luego aquí (15.12.2.5) se describe analíticamente cómo se elige el método más específico.
Entonces, de acuerdo con esto, por ejemplo, como se describe
Un método aplicable m1 es más específico que otro método aplicable m2, para una invocación con expresiones de argumento e1, ..., ek, si cualquiera de los siguientes es verdadero:
m2 es genérico, y se infiere que m1 es más específico que m2 para las expresiones de argumento e1, ..., ek
Entonces
// Callable -> why is it not a Supplier? execute(() -> null); <-- Callable shall be picked from 2 options as M2 is generic and M1 is inferred to be more specific void execute(Callable<Void> callable) { // <------ M1 try { callable.call(); } catch (Exception e) { e.printStackTrace(); } } <T> T execute(Supplier<T> supplier) { // <------ M2 is Generic return supplier.get(); }Por qué se infiere que M1 es más específico se puede rastrear a partir de este proceso descritoaquí (18.5.4 Inferencia de método más específico)