java-8

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.