lambdas - java 8 lambda explicacion
¿Por qué los consumidores aceptan lambdas con cuerpos de declaración pero no cuerpos de expresión? (3)
El siguiente código sorprendentemente se está compilando con éxito:
Consumer<String> p = ""::equals;
Esto también:
p = s -> "".equals(s);
Pero esto falla con el error
boolean cannot be converted to void
como se esperaba:
p = s -> true;
La modificación del segundo ejemplo con paréntesis también falla:
p = s -> ("".equals(s));
¿Es un error en el compilador de Java o hay una regla de inferencia de tipo que no conozco?
Creo que las otras respuestas complican la explicación al centrarse en lambdas, mientras que su comportamiento en este caso es similar al comportamiento de los métodos implementados manualmente. Esto compila:
new Consumer<String>() {
@Override
public void accept(final String s) {
"".equals(s);
}
}
mientras que esto no:
new Consumer<String>() {
@Override
public void accept(final String s) {
true;
}
}
porque
"".equals(s)
es una declaración, pero
true
no lo es.
Una expresión lambda para una interfaz funcional que devuelve void requiere una declaración para que siga las mismas reglas que el cuerpo de un método.
Tenga en cuenta que, en general, los cuerpos lambda no siguen
exactamente
las mismas reglas que los cuerpos de método; en particular, si una lambda cuyo cuerpo es una expresión implementa un método que devuelve un valor, tiene un
return
implícito.
Entonces, por ejemplo,
x -> true
sería una implementación válida de
Function<Object, Boolean>
, mientras que
true;
No es un cuerpo de método válido.
Pero en este caso particular, las interfaces funcionales y los cuerpos de los métodos coinciden.
Primero, vale la pena ver qué es realmente un
Consumer<String>
.
De la documentación
:
Representa una operación que acepta un único argumento de entrada y no devuelve ningún resultado . A diferencia de la mayoría de las otras interfaces funcionales, se espera que Consumer opere a través de efectos secundarios.
Por lo tanto, es una función que acepta una cadena y no devuelve nada.
Consumer<String> p = ""::equals;
Compila con éxito porque
equals
puede tomar una cadena (y, de hecho, cualquier objeto).
El resultado de igual es simplemente ignorado. *
p = s -> "".equals(s);
Esto es exactamente lo mismo, pero con una sintaxis diferente.
El compilador sabe que no debe agregar un
return
implícito porque un
Consumer
no debe devolver un valor.
Sin
embargo,
agregaría un
return
implícito si la lambda fuera una
Function<String, Boolean>
.
p = s -> true;
Esto toma una (
s
) cadena (
s
) pero como
true
es una expresión y no una declaración, el resultado no puede ignorarse de la misma manera.
El compilador
tiene que
agregar un
return
implícito porque una expresión no puede existir por sí sola.
Por lo tanto, esto tiene un retorno: un booleano.
Por lo tanto, no es un
Consumer
. **
p = s -> ("".equals(s));
Nuevamente, esta es una
expresión
, no una declaración.
Ignorando las lambdas por un momento, verá la línea
System.out.println("Hello");
tampoco se compilará si lo envuelve entre paréntesis.
* De la especificación :
Si el cuerpo de una lambda es una expresión de enunciado (es decir, una expresión que se le permitiría estar sola como enunciado), es compatible con un tipo de función que produce vacío; cualquier resultado simplemente se descarta.
** De la especificación (gracias, Eugene ):
Una expresión lambda es congruente con un tipo de función [que produce vacío] si ... el cuerpo lambda es una expresión de enunciado ( §14.8 ) o un bloque compatible con vacío.
s -> "".equals(s)
y
s -> true
no confíe en los mismos descriptores de funciones.
s -> "".equals(s)
puede referirse a
String->void
o
String->boolean
function descriptor.
s -> true
refiere solo a
String->boolean
descriptor de función
String->boolean
.
Por qué ?
-
cuando escribe
s -> "".equals(s)
, el cuerpo de la lambda:"".equals(s)
es una declaración que produce un valor .
El compilador considera que la función puede devolvervoid
oboolean
.
Entonces escribiendo:
Function<String, Boolean> function = s -> "".equals(s);
Consumer<String> consumer = s -> "".equals(s);
es válida.
Cuando asigna el cuerpo lambda a una variable declarada
Consumer<String>
, se utiliza el descriptor
String->void
.
Por supuesto, este código no tiene mucho sentido (verificas la igualdad y no usas el resultado) pero al compilador no le importa.
Es lo mismo cuando escribe una declaración:
myObject.getMyProperty()
donde
getMyProperty()
devuelve un valor
boolean
pero no almacena el resultado.
-
cuando escribe
s -> true
, el cuerpo de la lambda:true
es una sola expresión .
El compilador considera que la función devuelve necesariamenteboolean
.
Por lo tanto, solo se puede usar el descriptorString->boolean
.
Ahora, regrese a su código que no se compila.
Que estás tratando de hacer ?
Consumer<String> p = s -> true;
No se puede.
Desea asignar a una variable que utiliza el descriptor de función
Consumer<String>
un cuerpo lambda con el descriptor de función
String->void
.
¡No coincide!