java 8 - ¿Por qué una referencia de método Java con tipo de retorno coincide con la interfaz del consumidor?
java-8 (2)
Estoy confundido por el siguiente código
class LambdaTest {
public static void main(String[] args) {
Consumer<String> lambda1 = s -> {};
Function<String, String> lambda2 = s -> s;
Consumer<String> lambda3 = LambdaTest::consume; // but s -> s doesn''t work!
Function<String, String> lambda4 = LambdaTest::consume;
}
static String consume(String s) { return s;}
}
Hubiera esperado que la asignación de lambda3 fallara ya que mi método de consumo no coincide con el método de aceptación en la interfaz del consumidor: los tipos de retorno son diferentes, String vs. void.
Además, siempre pensé que existe una relación uno a uno entre las expresiones Lambda y las referencias de métodos, pero este no es el caso, como lo muestra mi ejemplo.
¿Podría alguien explicarme qué está pasando aquí?
Como Brian Goetz señaló en un comentario , la base de la decisión de diseño fue permitir adaptar un método a una interfaz funcional de la misma manera que puede llamar al método, es decir, puede llamar a todos los métodos de devolución de valores e ignorar el valor devuelto.
Cuando se trata de expresiones lambda, las cosas se vuelven un poco más complicadas.
Hay dos formas de expresiones lambda,
(args) -> expression
y
(args) -> { statements* }
.
Si la segunda forma es
void
, depende de la pregunta de si ninguna ruta de código intenta devolver un valor, por ejemplo
() -> { return ""; }
() -> { return ""; }
no es
void
, pero es compatible con expresiones, mientras que
() -> {}
o
() -> { return; }
() -> { return; }
son
void
compatibles.
Tenga en cuenta que
() -> { for(;;); }
() -> { for(;;); }
y
() -> { throw new RuntimeException(); }
() -> { throw new RuntimeException(); }
son ambos,
void
y compatible, ya que no se completan normalmente y no hay declaración de
return
.
La forma
(arg) -> expression
es compatible con el valor si la expresión se evalúa como un valor.
Pero también hay expresiones, que son
declaraciones
al mismo tiempo.
Estas expresiones pueden tener un efecto secundario y, por lo tanto, pueden escribirse como una declaración independiente para producir solo el efecto secundario, ignorando el resultado producido.
Del mismo modo, la forma
(arg) -> expression
puede ser
void
compatible, si la expresión también es una declaración.
Una expresión de la forma
s -> s
no puede ser
void
ya que
s
no es una declaración, es decir, no puede escribir
s -> { s; }
s -> { s; }
tampoco.
Por otro lado,
s -> s.toString()
puede ser
void
compatible, porque las invocaciones de métodos son declaraciones.
Del mismo modo,
s -> i++
puede ser
void
compatible ya que los incrementos se pueden usar como una declaración, por lo que
s -> { i++; }
s -> { i++; }
es válido también.
Por supuesto,
i
que ser un campo para que esto funcione, no una variable local.
La especificación del lenguaje Java
§14.8.
Las declaraciones de expresión
enumeran todas las expresiones que pueden usarse como declaraciones.
Además de las invocaciones de métodos y operadores de incremento / decremento ya mencionados, nombra las asignaciones y las expresiones de creación de instancia de clase, por lo que
s -> foo=s
s -> new WhatEver(s)
son
void
compatibles.
Como nota al margen, la forma
(arg) -> methodReturningVoid(arg)
es la
única
forma de expresión que no es compatible con valores.
consume(String)
método
consume(String)
coincide con
Consumer<String>
interfaz
Consumer<String>
, porque consume una
String
; el hecho de que devuelva un valor es irrelevante, ya que, en este caso, simplemente se ignora.
(Debido a que la interfaz del
Consumer
no espera ningún valor de retorno).
Debe haber sido una elección de diseño y básicamente una utilidad: imagine cuántos métodos tendrían que refactorizarse o duplicarse para satisfacer las necesidades de interfaces funcionales como
Consumer
o incluso el
Runnable
muy común.
(Tenga en cuenta que puede pasar cualquier método que no consuma parámetros como
Runnable
a un
Executor
, por ejemplo).
Incluso métodos como
java.util.List#add(Object)
devuelven un valor:
boolean
.
Ser incapaz de pasar tales referencias de métodos solo porque devuelven
algo
(que en su mayoría es irrelevante en muchos casos) sería bastante molesto.