Eu tenho uma lista de nomes. Na linha 3, tive que converter o resultado da expressão lambda para Predicate<String>
. O livro que estou lendo explica que o elenco é necessário para ajudar o compilador a determinar qual é a interface funcional correspondente.
No entanto, não preciso de um elenco desse tipo na linha a seguir, porque não ligo negate()
. Como isso faz alguma diferença? Eu entendo que negate()
aqui retorna Predicate<String>
, mas a expressão lambda anterior não faz o mesmo?
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()
chamado se não fosse possível inferir o tipo de atributo lambda que faz parte da expressão e, portanto, ainda mais incapaz de estabelecer que a expressão signifique aPredicate<String>
.Respostas:
Não é bem só porque você liga
negate()
. Dê uma olhada nesta versão, que é muito próxima da sua, mas compila:A diferença entre isso e sua versão? É sobre como as expressões lambda obtêm seus tipos (o "tipo de destino").
O que você acha que isso faz?
Sua resposta atual é que "ele chama
negate()
oPredicate<String>
dado pela expressãostr -> str.length() <= 5
". Direita? Mas isso é apenas porque é isso que você pretende que ele faça. O compilador não sabe disso . Por quê? Porque poderia ser qualquer coisa. Minha própria resposta para a pergunta acima pode ser "ela chamanegate
meu outro tipo de interface funcional ... (sim, o exemplo será um pouco bizarro)Eu poderia usar a mesma expressão lambda,
names.removeIf((str -> str.length() <= 5).negate());
mas com o significadostr -> str.length() <= 5
de ser umSentenceExpression
e não umPredicate<String>
.Explicação:
(str -> str.length() <= 5).negate()
não criastr -> str.length() <= 5
aPredicate<String>
. E foi por isso que eu disse que poderia ser qualquer coisa, incluindo minha interface funcional acima.De volta ao Java ... É por isso que as expressões lambda têm o conceito de "tipo de destino", que define a mecânica pela qual uma expressão lambda é entendida pelo compilador como um determinado tipo de interface funcional (ou seja, como você ajuda o compilador a saber que a expressão é
Predicate<String>
mais do queSentenceExpression
ou qualquer outra coisa que poderia ser). Você pode achar útil ler O que se entende por tipo de destino lambda e contexto de tipo de destino em Java? e Java 8: digitação de destinoUm dos contextos nos quais os tipos de destino são inferidos (se você ler as respostas nessas postagens) é o contexto de chamada, em que você passa uma expressão lambda como argumento para um parâmetro de um tipo de interface funcional, e é isso que é aplicável a
names.removeIf(((str -> str.length() <= 5)));
: é apenas a expressão lambda dada como argumento para um método que usa aPredicate<String>
. Isso não se aplica à instrução que não está compilando.Então, em outras palavras ...
names.removeIf(str -> str.length() <= 5);
usa uma expressão lambda em um local onde o tipo de argumento define claramente qual é o tipo de expressão lambda esperado (ou seja, o tipo de destinostr -> str.length() <= 5
é claramentePredicate<String>
).No entanto,
(str -> str.length() <= 5).negate()
não é uma expressão lambda, é apenas uma expressão que usa uma expressão lambda. Isso significa questr -> str.length() <= 5
, nesse caso, não está no contexto de chamada que determina o tipo de destino da expressão lambda (como no caso de sua última instrução). Sim, o compilador sabe queremoveIf
precisa de aPredicate<String>
e sabe com certeza que toda a expressão passada para o método deve ser aPredicate<String>
, mas não pressupõe que qualquer expressão lambda na expressão de argumento seja aPredicate<String>
(mesmo que você a trate) como predicado chamandonegate()
-o; poderia ter sido qualquer coisa compatível com a expressão lambda).É por isso que é necessário digitar seu lambda com uma conversão explícita (ou não, como no primeiro contra-exemplo que eu dei).
fonte
Predicate<String>
que é o que o compilador não conseguiu inferir em primeiro lugar devido ao uso denegate
si mesmo, conforme explicado nesta resposta.(str -> str.length() <= 5).negate()
não produzstr -> str.length() <= 5
umPredicate<String>
, mesmo que seja isso que você pretende.Não sei por que isso deve ser tão confuso. Isso pode ser explicado por 2 razões, IMO.
Vou deixar você descobrir o que isso significa e quais
JLS
são as palavras ao seu redor. Mas, em essência, são como genéricos:qual é o tipo
T
aqui? Bem, isto depende. Se você fizer :Dizem-se que expressões poli dependem do contexto de onde são usadas. As expressões lambda são as mesmas. Vamos considerar a expressão lambda isoladamente aqui:
quando você olha para isso, o que é isso? Bem, é um
Predicate<String>
? Mas pode ser AFunction<String, Boolean>
? Ou pode ser parMyTransformer<String, Boolean>
, onde:As escolhas são infinitas.
.negate()
chamado diretamente poderia ser uma opção.A partir das 10_000 milhas acima, você está correto: você está fornecendo isso
str -> str.length() <= 5
a umremoveIf
método, que aceita apenas aPredicate
. Não há maisremoveIf
métodos, portanto, o compilador deve ser capaz de "fazer a coisa correta" quando você fornecer isso(str -> str.length() <= 5).negate()
.Então, como é que isso não funciona? Vamos começar com o seu comentário:
Parece que é aqui que o principal problema começa, simplesmente não é assim que
javac
funciona. Não pode pegar o todostr -> str.length() <= 5).negate()
, diga a si mesmo que esse é umPredicate<String>
(já que você o está usando como argumentoremoveIf
) e depois decomponha ainda mais a parte sem.negate()
e veja se issoPredicate<String>
também é.javac
age de maneira inversa, precisa conhecer o alvo para saber se é legal ligarnegate
ou não.Além disso, você precisa fazer uma distinção clara entre expressões poli e expressões , em geral.
str -> str.length() <= 5).negate()
é uma expressão,str -> str.length() <= 5
é uma expressão poli.Pode haver idiomas em que as coisas são feitas de maneira diferente e onde isso é possível,
javac
simplesmente não é desse tipo.fonte
No exemplo a seguir
a expressão retorna verdadeira. Se no exemplo a seguir sem o elenco,
true
não souber nada sobre o métodonegate()
Por outro lado,
A expressão é convertida em
Predicate<String>
para informar ao compilador onde encontrar o métodonegate
. Então é o métodonegate()
que realmente faz a avaliação pelo seguinte:Observe que você pode obter o mesmo resultado sem o elenco da seguinte maneira:
fonte
Sem aprofundar as coisas, o ponto principal é que o lambda
str -> str.length() <= 5
não é necessariamente um,Predicate<String>
como Eugene explicou.Dito isso,
negate()
é uma função membro doPredicate<T>
e o compilador não pode usar a chamadanegate()
para ajudar a inferir o tipo em que o lambda deve ser interpretado. Mesmo que tentasse, poderia haver problemas, porque se várias classes possíveis tivessemnegate()
funções, não saberia qual escolher.Imagine que você é o compilador.
str -> str.length() <= 5
. Você sabe que é lambda que pega uma String e retorna um booleano, mas não sabe especificamente qual o tipo que ela representa..negate()
mas porque não sabe o tipo da expressão à esquerda do "." você precisa relatar um erro, pois não pode usar a chamada para negar para ajudar a inferir o tipo.Se o negado tivesse sido implementado como uma função estática, as coisas funcionariam. Por exemplo:
permitiria escrever
Como a escolha da função negar é inequívoca, o compilador sabe que precisa ser interpretado
str -> str.length() <= 5
comoPredicate<T>
ae pode coagir o tipo corretamente.Eu espero que isso ajude.
fonte