Como negar um predicado de referência de método

331

No Java 8, você pode usar uma referência de método para filtrar um fluxo, por exemplo:

Stream<String> s = ...;
long emptyStrings = s.filter(String::isEmpty).count();

Existe uma maneira de criar uma referência de método que é a negação de uma existente, ou seja, algo como:

long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

Eu poderia criar o notmétodo como abaixo, mas queria saber se o JDK oferecia algo semelhante.

static <T> Predicate<T> not(Predicate<T> p) { return o -> !p.test(o); }
assylias
fonte
6
JDK-8050818 cobre a adição de um Predicate.not(Predicate)método estático . Mas como esse problema ainda está aberto, veremos isso o mais cedo possível no Java 12 (se houver).
21418 Stefan Zobel
1
Parece que esta resposta poderia ser a solução definitiva também adaptada no JDK / 11.
Naman
2
Eu realmente gostaria de ver uma sintaxe especial referência método para este caso: s.filter (! Cadeia :: isEmpty)
Mike Twain

Respostas:

178

Predicate.not( … )

oferece um novo método Predicado # não

Portanto, você pode negar a referência do método:

Stream<String> s = ...;
long nonEmptyStrings = s.filter(Predicate.not(String::isEmpty)).count();
Anton Balaniuc
fonte
214

Estou planejando importar estática o seguinte para permitir que a referência do método seja usada em linha:

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

por exemplo

Stream<String> s = ...;
long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

Atualização : A partir do Java-11, o JDK também oferece uma solução semelhante .

davidillsley
fonte
9
@SaintHill mas então você tem que escrever para fora, dando o parâmetro um nome
FLUP
5
Link do Goia
Henrik Aasted Sørensen
150

Existe uma maneira de compor uma referência de método que é o oposto de uma referência de método atual. Veja a resposta da @ vlasec abaixo, que mostra como Predicateconverter explicitamente a referência de método para a e depois convertê-la usando a negatefunção Essa é uma maneira dentre algumas outras maneiras não muito problemáticas de fazê-lo.

O oposto disso:

Stream<String> s = ...;
int emptyStrings = s.filter(String::isEmpty).count();

é isto:

Stream<String> s = ...;
int notEmptyStrings = s.filter(((Predicate<String>) String::isEmpty).negate()).count()

ou isto:

Stream<String> s = ...;
int notEmptyStrings = s.filter( it -> !it.isEmpty() ).count();

Pessoalmente, prefiro a técnica posterior porque acho mais claro ler do it -> !it.isEmpty()que um elenco explícito longo e explícito e depois nego.

Também se poderia fazer um predicado e reutilizá-lo:

Predicate<String> notEmpty = (String it) -> !it.isEmpty();

Stream<String> s = ...;
int notEmptyStrings = s.filter(notEmpty).count();

Ou, se tiver uma coleção ou matriz, use um loop for que seja simples, tenha menos sobrecarga e * possa ser ** mais rápido:

int notEmpty = 0;
for(String s : list) if(!s.isEmpty()) notEmpty++;

* Se você quiser saber o que é mais rápido, use JMH http://openjdk.java.net/projects/code-tools/jmh e evite o código de referência manual, a menos que evite todas as otimizações da JVM - consulte Java 8: desempenho do Streams vs Colecções

** Estou recebendo críticas por sugerir que a técnica de loop for é mais rápida. Elimina a criação de um fluxo, elimina o uso de outra chamada de método (função negativa para predicado) e elimina uma lista / contador de acumuladores temporários. Portanto, algumas coisas que são salvas pela última construção que podem torná-la mais rápida.

Eu acho que é mais simples e agradável, mesmo que não seja mais rápido. Se o trabalho exigir um martelo e um prego, não traga uma serra elétrica e cola! Eu sei que alguns de vocês têm problemas com isso.

lista de desejos: Gostaria de ver as Streamfunções Java evoluírem um pouco agora que os usuários Java estão mais familiarizados com elas. Por exemplo, o método 'count' no Stream pode aceitar um Predicatepara que isso possa ser feito diretamente assim:

Stream<String> s = ...;
int notEmptyStrings = s.count(it -> !it.isEmpty());

or

List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());
O coordenador
fonte
Por que você diz que é muito mais rápido ?
José Andias
@ JoséAndias (1) É mais rápido ou 'muito mais rápido'? (2) Se sim, por quê? O que você determinou?
O Coordenador
3
Estou pedindo para você elaborar "muito mais rápido para executar". As perguntas: (1) É mais rápido ou 'muito mais rápido'? (2) Se sim, por quê? O que você determinou? são melhor respondidas por você, o autor da declaração. Não estou considerando que seja mais rápido ou mais lento. Obrigado
José Andias
2
Depois, jogarei isso para sua consideração - elimina a criação de um fluxo, elimina o uso de outra chamada de método (função negativa para predicado) e elimina uma lista / contador temporário de acumuladores. Então, algumas coisas são salvas pela última construção. Não tenho certeza se é mais rápido ou muito mais rápido, mas presumo que seja 'muito' mais rápido. Mas talvez "muito" seja subjetivo. É mais simples codificar mais tarde do que criar predicados e fluxos negativos para fazer uma contagem direta. Minha preferência .
O Coordenador
4
negate () parece ser uma solução ideal. Pena que não é estática como Predicate.negate(String::isEmpty);sem o elenco pesado.
Joel Shemtov
92

Predicatetem métodos and, ore negate.

No entanto, String::isEmptynão é um Predicate, é apenas um String -> Booleanlambda e ainda pode se tornar qualquer coisa, por exemplo Function<String, Boolean>. Inferência de tipo é o que precisa acontecer primeiro. O filtermétodo deduz o tipo implicitamente . Mas se você negar antes de passar como argumento, isso não acontece mais. Como o @axtavt mencionou, a inferência explícita pode ser usada como uma maneira feia:

s.filter(((Predicate<String>) String::isEmpty).negate()).count()

Existem outras maneiras recomendadas em outras respostas, com notmétodo estático e lambda provavelmente as melhores idéias. Isso conclui a seção tl; dr .


No entanto, se você quiser uma compreensão mais profunda da inferência do tipo lambda, gostaria de explicar um pouco mais a fundo, usando exemplos. Olhe para eles e tente descobrir o que acontece:

Object obj1                  = String::isEmpty;
Predicate<String> p1         = s -> s.isEmpty();
Function<String, Boolean> f1 = String::isEmpty;
Object obj2                  = p1;
Function<String, Boolean> f2 = (Function<String, Boolean>) obj2;
Function<String, Boolean> f3 = p1::test;
Predicate<Integer> p2        = s -> s.isEmpty();
Predicate<Integer> p3        = String::isEmpty;
  • obj1 não compila - lambdas precisa inferir uma interface funcional (= com um método abstrato)
  • p1 e f1 funcionam bem, cada um inferindo um tipo diferente
  • obj2 lança um Predicatepara Object- bobagem, mas válida
  • f2 falhar em tempo de execução - você não pode lançar Predicatepara Function, ele não é mais sobre inferência
  • f3 funciona - você chama o método do predicado testdefinido por sua lambda
  • p2 não compila - Integernão possui isEmptymétodo
  • O p3 também não compila - não há String::isEmptymétodo estático com Integerargumento

Espero que isso ajude a obter mais informações sobre como funciona a inferência de tipo.

Vlasec
fonte
46

Com base nas respostas de outros e na experiência pessoal:

Predicate<String> blank = String::isEmpty;
content.stream()
       .filter(blank.negate())
Jose Alban
fonte
4
Interessante - você não pode incorporar a ::referência funcional como se poderia desejar ( String::isEmpty.negate()), mas se você atribuir a uma variável primeiro (ou converter a Predicate<String>primeira), isso funcionará. Acho que o lambda !será mais legível na maioria dos casos, mas é útil saber o que pode e o que não pode ser compilado.
Joshua Goldberg
2
@ JoshuaGoldberg, expliquei isso na minha resposta: a referência do método não é um Predicado por si só. Aqui, a conversão é feita pela variável
Vlasec 31/10/16
17

Outra opção é utilizar a conversão lambda em contextos não ambíguos em uma classe:

public static class Lambdas {
    public static <T> Predicate<T> as(Predicate<T> predicate){
        return predicate;
    }

    public static <T> Consumer<T> as(Consumer<T> consumer){
        return consumer;
    }

    public static <T> Supplier<T> as(Supplier<T> supplier){
        return supplier;
    }

    public static <T, R> Function<T, R> as(Function<T, R> function){
        return function;
    }

}

... e, em seguida, estática, importe a classe do utilitário:

stream.filter(as(String::isEmpty).negate())
Askar Kalykov
fonte
1
Na verdade, estou surpreso que isso funcione - mas parece que o JDK favorece o Predicado <T> sobre a Função <T, Boolean>. Mas você não fará com que o Lambdas projete nada na Função <T, Boolean>.
Vlasec
Ele funciona para String, mas não para List: Erro: (20, 39) java: a referência a como é ambígua, tanto o método <T> como (java.util.function.Consumer <T>) em com.strands.sbs.function. lambdas e método <T, R> como (java.util.function.Function <T, R>) em com.strands.sbs.function.Lambdas jogo
Daniel Pinyol
Daniel, que poderia acontecer se você está tentando usar método sobrecarregado :)
Askar Kalykov
Agora que entendo a inferência de tipo muito melhor do que originalmente, entendo como ela funciona. Basicamente, apenas encontra a única opção que funciona. Parece interessante, só não sei se existe algum nome melhor que não cause clichê.
Vlasec
12

Não deve Predicate#negateser o que você está procurando?

Marco13
fonte
Você precisa obter um Predicateprimeiro.
Sotirios Delimanolis 31/01
21
Você tem que elenco String::isEmpty()para Predicate<String>antes - é muito feio.
precisa saber é o seguinte
3
@assylias Use como Predicate<String> p = (Predicate<String>) String::isEmpty;e p.negate().
Sotirios Delimanolis 31/01
8
@SotiriosDelimanolis eu sei, mas isso derrota o objetivo - prefiro escrever s -> !s.isEmpty()nesse caso!
assylias 31/01
@assylias: Sim, acredito que essa seja realmente a ideia; que apenas escrever o lambda longhand é o fallback pretendido.
Louis Wasserman
8

Nesse caso, você poderia usar o org.apache.commons.lang3.StringUtilse

int nonEmptyStrings = s.filter(StringUtils::isNotEmpty).count();
fora dos limites
fonte
6
Não. A questão é como negar qualquer referência de método e toma String::isEmptycomo exemplo. Ainda são informações relevantes se você tiver esse caso de uso, mas se ele responder apenas ao caso de uso String, ele não deverá ser aceito.
Anthony Drogon
4

Eu escrevi uma classe de utilitário completa (inspirada na proposta de Askar) que pode pegar a expressão lambda do Java 8 e transformá-la (se aplicável) em qualquer lambda padrão do Java 8 definido no pacote java.util.function. Você pode, por exemplo, fazer:

  • asPredicate(String::isEmpty).negate()
  • asBiPredicate(String::equals).negate()

Como haveria inúmeras ambiguidades se todos os métodos estáticos fossem nomeados apenas as(), optei por chamar o método "como" seguido pelo tipo retornado. Isso nos dá controle total da interpretação lambda. Abaixo está a primeira parte da classe de utilidade (um tanto grande) que revela o padrão usado.

Dê uma olhada na turma completa aqui (no gist).

public class FunctionCastUtil {

    public static <T, U> BiConsumer<T, U> asBiConsumer(BiConsumer<T, U> biConsumer) {
        return biConsumer;
    }

    public static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> biFunction) {
        return biFunction;
    }

     public static <T> BinaryOperator<T> asBinaryOperator(BinaryOperator<T> binaryOperator) {
        return binaryOperator;
    }

    ... and so on...
}
Per-Åke Minborg
fonte
4

Você pode usar Predicados das Coleções Eclipse

MutableList<String> strings = Lists.mutable.empty();
int nonEmptyStrings = strings.count(Predicates.not(String::isEmpty));

Se você não pode alterar as strings de List:

List<String> strings = new ArrayList<>();
int nonEmptyStrings = ListAdapter.adapt(strings).count(Predicates.not(String::isEmpty));

Se você só precisa de uma negação, String.isEmpty()você também pode usar StringPredicates.notEmpty().

Nota: Sou colaborador do Eclipse Collections.

Nikhil Nanivadekar
fonte
1

Você pode fazer isso contanto emptyStrings = s.filter(s->!s.isEmpty()).count();

Narsi Reddy Nallamilli
fonte
0

Se você estiver usando o Spring Boot (2.0.0+), poderá usar:

import org.springframework.util.StringUtils;

...
.filter(StringUtils::hasLength)
...

O que faz: return (str != null && !str.isEmpty());

Portanto, terá o efeito de negação necessário para isEmpty

Gilad Peleg
fonte