Tengo una lista de nombres. En la línea 3, tuve que enviar el resultado de la expresión lambda a Predicate<String>
. El libro que estoy leyendo explica que el reparto es necesario para ayudar al compilador a determinar cuál es la interfaz funcional correspondiente.
Sin embargo, no necesito tal reparto en la siguiente línea porque no llamo negate()
. ¿Cómo hace esto alguna diferencia? Entiendo que negate()
aquí regresa Predicate<String>
, pero ¿la expresión lambda anterior no hace lo mismo?
List<String> names = new ArrayList<>();
//add various names here
names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required
names.removeIf(((str -> str.length() <= 5))); //compiles without cast
Predicate<String> predicate = str -> str.length() <= 5; names.removeIf(predicate.negate()); names.removeIf(predicate);
negate()
recurriría si no es capaz de inferir el tipo del atributo lambda que forma parte de la expresión y, por lo tanto, no puede establecer que la expresión significa aPredicate<String>
?Respuestas:
No es solo porque llamas
negate()
. Eche un vistazo a esta versión, que es muy parecida a la suya pero que compila:¿La diferencia entre esto y tu versión? Se trata de cómo las expresiones lambda obtienen sus tipos (el "tipo de destino").
¿Qué crees que hace esto?
Su respuesta actual es que "invoca
negate()
loPredicate<String>
dado por la expresiónstr -> str.length() <= 5
". ¿Derecha? Pero eso es solo porque esto es lo que querías que hiciera. El compilador no lo sabe . ¿Por qué? Porque podría ser cualquier cosa. Mi propia respuesta a la pregunta anterior podría ser "llamanegate
a mi otro tipo de interfaz funcional ... (sí, el ejemplo será un poco extraño)Podría usar la misma expresión lambda
names.removeIf((str -> str.length() <= 5).negate());
pero que significastr -> str.length() <= 5
serSentenceExpression
más que unPredicate<String>
.Explicación:
(str -> str.length() <= 5).negate()
no hacestr -> str.length() <= 5
aPredicate<String>
. Y es por eso que dije que podría ser cualquier cosa, incluida mi interfaz funcional anterior.De vuelta a Java ... Esta es la razón por la cual las expresiones lambda tienen el concepto de "tipo de destino", que define la mecánica por la cual el compilador entiende una expresión lambda como un tipo de interfaz funcional dado (es decir, cómo ayuda al compilador a saber que la expresión es en
Predicate<String>
lugar deSentenceExpression
o cualquier otra cosa que podría ser). Puede resultarle útil leer ¿Qué se entiende por tipo de destino lambda y contexto de tipo de destino en Java? y Java 8: mecanografía de destinoUno de los contextos en los que se infieren los tipos de destino (si lee las respuestas en esas publicaciones) es el contexto de invocación, donde pasa una expresión lambda como argumento para un parámetro de un tipo de interfaz funcional, y eso es lo que es aplicable a
names.removeIf(((str -> str.length() <= 5)));
: es solo la expresión lambda dada como argumento de un método que toma aPredicate<String>
. Esto no se aplica a la declaración que no se está compilando.Entonces, en otras palabras ...
names.removeIf(str -> str.length() <= 5);
utiliza una expresión lambda en un lugar donde el tipo de argumento define claramente cuál es el tipo de expresión lambda que se espera que sea (es decir, el tipo de destinostr -> str.length() <= 5
es claramentePredicate<String>
).Sin embargo,
(str -> str.length() <= 5).negate()
no es una expresión lambda, es solo una expresión que utiliza una expresión lambda. Esto quiere decir questr -> str.length() <= 5
en este caso no está en el contexto de invocación que determina el tipo de destino de la expresión lambda (como en el caso de su última declaración). Sí, el compilador sabe queremoveIf
necesita unPredicate<String>
, y sabe con certeza que toda la expresión pasada al método tiene que ser unPredicate<String>
, pero no asumiría que cualquier expresión lambda en la expresión de argumento sería unPredicate<String>
(incluso si lo trata como predicado al invocarlonegate()
; podría haber sido cualquier cosa que sea compatible con la expresión lambda).Es por eso que es necesario escribir su lambda con un molde explícito (o de lo contrario, como en el primer contraejemplo que di).
fuente
Predicate<String>
que es lo que el compilador no ha podido inferir en primer lugar debido al uso denegate
sí mismo como se explica en esta respuesta.(str -> str.length() <= 5).negate()
no lo hacestr -> str.length() <= 5
unaPredicate<String>
, incluso si eso es lo que pretende.No sé por qué esto tiene que ser tan confuso. Esto se puede explicar con 2 razones, la OMI.
Dejaré que descubras qué significa esto y cuáles son las
JLS
palabras que lo rodean. Pero en esencia, estos son como genéricos:¿Cuál es el tipo
T
aquí? Bueno, eso depende. Si lo haces :Se dice que las expresiones poli dependen del contexto de donde se usan. Las expresiones lambda son iguales. Tomemos la expresión lambda de forma aislada aquí:
cuando lo miras, ¿qué es esto? Bueno es un
Predicate<String>
? Pero puede ser AFunction<String, Boolean>
? O puede ser inclusoMyTransformer<String, Boolean>
, donde:Las opciones son infinitas.
.negate()
llamar directamente podría ser una opción.Desde 10_000 millas arriba, está en lo correcto: está proporcionando eso
str -> str.length() <= 5
a unremoveIf
método, que solo acepta aPredicate
. No hay másremoveIf
métodos, por lo que el compilador debería ser capaz de "hacer lo correcto" cuando lo proporcione(str -> str.length() <= 5).negate()
.Entonces, ¿cómo es que esto no funciona? Comencemos con tu comentario:
Parece que aquí es donde comienza el problema principal, simplemente no es así como
javac
funciona. No puede tomar todostr -> str.length() <= 5).negate()
, decirse a sí mismo que esto es unPredicate<String>
(ya que lo está utilizando como argumento pararemoveIf
) y luego descomponer aún más la parte sin.negate()
y ver si esoPredicate<String>
también es un .javac
actúa a la inversa, necesita conocer el objetivo para poder saber si es legal llamarnegate
o no.También debe hacer una distinción clara entre expresiones poli y expresiones , en general.
str -> str.length() <= 5).negate()
es una expresión,str -> str.length() <= 5
es una expresión poli.Puede haber idiomas donde las cosas se hacen de manera diferente y donde esto es posible,
javac
simplemente no es ese tipo.fuente
En el siguiente ejemplo
La expresión devuelve verdadero. Si en el siguiente ejemplo sin el elenco,
true
no sabe nada sobre el métodonegate()
Por otra parte,
La expresión se convierte en
Predicate<String>
para decirle al compilador dónde encontrar el métodonegate
. Entonces es el métodonegate()
que realmente realiza la evacuación de la siguiente manera:Tenga en cuenta que podría lograr el mismo resultado sin el elenco de la siguiente manera:
fuente
Sin entrar demasiado en las cosas, la conclusión es que la lambda
str -> str.length() <= 5
no es necesariamente una,Predicate<String>
como explicó Eugene .Dicho esto,
negate()
es una función miembro dePredicate<T>
y el compilador no puede usar la llamada anegate()
para ayudar a inferir el tipo con el que se debe interpretar el lambda. Incluso si lo intentara, podría haber problemas porque si varias clases posibles tuvierannegate()
funciones, no sabría cuál elegir.Imagina que eres el compilador.
str -> str.length() <= 5
. Usted sabe que es lambda que toma una cadena y devuelve un booleano, pero no sabe específicamente qué tipo representa..negate()
pero porque no conoce el tipo de expresión a la izquierda del "." debe informar un error ya que no puede usar la llamada para negar para ayudar a inferir el tipo.Si se hubiera negado como una función estática, las cosas funcionarían. Por ejemplo:
te permitiría escribir
Debido a que la elección de la función de negación no es ambigua, el compilador sabe que necesita interpretar
str -> str.length() <= 5
comoPredicate<T>
ay puede forzar el tipo correctamente.Espero que esto ayude.
fuente