expresiones - java 8 tutorial español
¿Por qué este Java 8 lambda no se compila? (4)
El siguiente código Java no se compila:
@FunctionalInterface
private interface BiConsumer<A, B> {
void accept(A a, B b);
}
private static void takeBiConsumer(BiConsumer<String, String> bc) { }
public static void main(String[] args) {
takeBiConsumer((String s1, String s2) -> new String("hi")); // OK
takeBiConsumer((String s1, String s2) -> "hi"); // Error
}
El compilador informa:
Error:(31, 58) java: incompatible types: bad return type in lambda expression
java.lang.String cannot be converted to void
Lo extraño es que la línea marcada "OK" se compila bien, pero la línea marcada "Error" falla. Parecen esencialmente idénticos.
Básicamente, la
new String("hi")
es un código ejecutable que realmente hace algo (crea una nueva cadena y luego la devuelve).
El valor devuelto se puede ignorar y la
new String("hi")
todavía se puede utilizar en lambda de retorno nulo para crear una nueva cadena.
Sin embargo,
"hi"
es solo una constante que no hace nada por sí misma.
Lo único razonable que se puede hacer con él en el cuerpo lambda es
devolverlo
.
Pero el método lambda tendría que tener el tipo de retorno
String
u
Object
, pero devuelve
void
, por lo tanto,
String cannot be casted to void
error
String cannot be casted to void
.
El JLS especifica que
Si el resultado del tipo de función es nulo, el cuerpo lambda es una expresión de declaración (§14.8) o un bloque nulo compatible.
Ahora veamos eso en detalle,
Como su método
takeBiConsumer
es de tipo nulo, la lambda que recibe una
new String("hi")
lo interpretará como un bloque como
{
new String("hi");
}
que es válido en un vacío, de ahí la compilación del primer caso.
Sin embargo, en el caso donde el lambda es
-> "hi"
, un bloque como
{
"hi";
}
no es una sintaxis válida en java. Por lo tanto, lo único que se puede hacer con "hola" es intentar devolverlo.
{
return "hi";
}
que no es válido en un vacío y explica el mensaje de error
incompatible types: bad return type in lambda expression
java.lang.String cannot be converted to void
Para una mejor comprensión, tenga en cuenta que si cambia el tipo de
takeBiConsumer
a una cadena,
-> "hi"
será válido, ya que simplemente intentará devolver directamente la cadena.
Tenga en cuenta que al principio pensé que el error fue causado por el lambda al estar en un contexto de invocación incorrecto, por lo que compartiré esta posibilidad con la comunidad:
Es un error en tiempo de compilación si se produce una expresión lambda en un programa en un lugar que no sea un contexto de asignación (§5.2), un contexto de invocación (§5.3) o un contexto de conversión (§5.5).
Sin embargo, en nuestro caso, estamos en un contexto de invocación que es correcto.
El primer caso está bien porque está invocando un método "especial" (un constructor) y no está tomando el objeto creado. Solo para que quede más claro, pondré las llaves opcionales en sus lambdas:
takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error
Y más claro, lo traduciré a la notación anterior:
takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
public void accept(String s, String s2) {
new String("hi"); // OK
}
});
takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
public void accept(String s, String s2) {
"hi"; // Here, the compiler will attempt to add a "return"
// keyword before the "hi", but then it will fail
// with "compiler error ... bla bla ...
// java.lang.String cannot be converted to void"
}
});
En el primer caso, está ejecutando un constructor, pero NO está devolviendo el objeto creado, en el segundo caso está intentando devolver un valor de Cadena, pero su método en su interfaz
BiConsumer
devuelve vacío, de ahí el error del compilador.
Su lambda debe ser congruente con
BiConsumer<String, String>
.
Si se refiere a
JLS # 15.27.3 (Tipo de Lambda)
:
Una expresión lambda es congruente con un tipo de función si se cumple todo lo siguiente:
- [...]
- Si el resultado del tipo de función es nulo, el cuerpo lambda es una expresión de declaración (§14.8) o un bloque nulo compatible.
Entonces, el lambda debe ser una expresión de declaración o un bloque vacío compatible:
- Una invocación de constructor es una expresión de declaración, por lo que se compila.
- Un literal de cadena no es una expresión de declaración y no es nulo compatible (véanse los ejemplos en 15.27.2 ), por lo que no se compila.