Come negare un predicato di riferimento del metodo


331

In Java 8, è possibile utilizzare un riferimento al metodo per filtrare un flusso, ad esempio:

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

C'è un modo per creare un riferimento al metodo che è la negazione di uno esistente, cioè qualcosa di simile:

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

Potrei creare il notmetodo come sotto ma mi chiedevo se il JDK offrisse qualcosa di simile.

static <T> Predicate<T> not(Predicate<T> p) { return o -> !p.test(o); }

6
JDK-8050818 copre l'aggiunta di un Predicate.not(Predicate)metodo statico . Ma quel problema è ancora aperto, quindi lo vedremo al più presto in Java 12 (se mai).
Stefan Zobel,

1
Sembra che questa risposta potrebbe essere la soluzione definitiva adattata anche in JDK / 11.
Naman,

2
Mi piacerebbe davvero vedere una sintassi di riferimento del metodo speciale per questo caso: s.filter (String ::! IsEmpty)
Mike Twain

Risposte:


178

Predicate.not( … )

offre un nuovo metodo Predicate # not

Quindi puoi negare il riferimento al metodo:

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

214

Sto pianificando di importare staticamente quanto segue per consentire l'utilizzo in linea del riferimento al metodo:

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

per esempio

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

Aggiornamento : a partire da Java-11, JDK offre anche una soluzione simile integrata.


9
@SaintHill ma poi devi scriverlo, dando un nome al parametro
flup



150

C'è un modo per comporre un riferimento di metodo che è l'opposto di un riferimento di metodo corrente. Vedi la risposta di @ vlasec di seguito che mostra come eseguire il cast esplicito del riferimento al metodo a Predicatee quindi convertirlo utilizzando la negatefunzione. Questo è un modo tra alcuni altri modi non troppo fastidiosi per farlo.

Il contrario di questo:

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

è questo:

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

o questo:

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

Personalmente, preferisco la tecnica successiva perché trovo più chiaro da leggere it -> !it.isEmpty()rispetto a un cast esplicito lungo verboso e quindi negare.

Si potrebbe anche creare un predicato e riutilizzarlo:

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

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

Oppure, se si dispone di una raccolta o di un array, utilizzare semplicemente un ciclo continuo che è semplice, ha un sovraccarico minore e * potrebbe essere ** più veloce:

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

* Se vuoi sapere cosa è più veloce, usa JMH http://openjdk.java.net/projects/code-tools/jmh ed evita il codice benchmark manuale a meno che non eviti tutte le ottimizzazioni JVM - vedi Java 8: performance of Streams vs Collezioni

** Mi sta venendo voglia di suggerire che la tecnica for-loop sia più veloce. Elimina la creazione di un flusso, elimina l'utilizzo di un'altra chiamata di metodo (funzione negativa per predicato) ed elimina un elenco / contatore di accumulatori temporanei. Quindi alcune cose che vengono salvate dall'ultimo costrutto potrebbero renderlo più veloce.

Penso che sia più semplice e più bello, anche se non più veloce. Se il lavoro richiede un martello e un chiodo, non introdurre motosega e colla! So che alcuni di voi mettono in dubbio questo.

lista dei desideri: Mi piacerebbe vedere le Streamfunzioni Java evolversi un po 'ora che gli utenti Java ne hanno più familiarità. Ad esempio, il metodo 'count' in Stream potrebbe accettare un Predicatemodo che questo possa essere fatto direttamente in questo modo:

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

or

List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());

Perché dici che è molto più veloce ?
José Andias,

@ JoséAndias (1) È più veloce o "molto più veloce"? (2) In tal caso, perché? Che cosa hai determinato?
Il coordinatore

3
Ti sto chiedendo di elaborare "molto più velocemente per correre". Le domande: (1) È più veloce o "molto più veloce"? (2) In tal caso, perché? Che cosa hai determinato? hai una risposta migliore da te, l'autore della dichiarazione. Non lo considero più veloce o più lento. Grazie
José Andias,

2
Quindi lo prenderò in considerazione: elimina la creazione di un flusso, elimina l'utilizzo di un'altra chiamata di metodo (funzione negativa per predicato) ed elimina un elenco / contatore di accumulatori temporanei. Quindi alcune cose che vengono salvate dall'ultimo costrutto. Non sono sicuro se sia più veloce o quanto più veloce, ma suppongo che sia "molto" più veloce. Ma forse "molto" è soggettivo. È più semplice codificare il ritardo rispetto alla creazione di predicati e flussi negativi per fare un conteggio diretto. La mia preferenza .
Il coordinatore

4
negate () sembra una soluzione ideale. Peccato che non sia statico come Predicate.negate(String::isEmpty);senza il casting ingombrante.
Joel Shemtov,

92

Predicateha metodi and, ore negate.

Tuttavia, String::isEmptynon è un Predicate, è solo un String -> Booleanlambda e potrebbe ancora diventare qualsiasi cosa, ad es Function<String, Boolean>. L'inferenza del tipo è ciò che deve accadere per primo. Il filtermetodo deduce implicitamente il tipo . Ma se lo neghi prima di passarlo come argomento, non succede più. Come menzionato @axtavt, l' inferenza esplicita può essere usata come un brutto modo:

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

Ci sono altri modi consigliati in altre risposte, con notmetodo statico e lambda probabilmente sono le idee migliori. Questo conclude la sezione tl; dr .


Tuttavia, se vuoi una comprensione più approfondita dell'inferenza del tipo lambda, vorrei spiegarlo un po 'più a fondo, usando esempi. Guarda questi e prova a capire cosa succede:

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 non viene compilato - lambdas deve inferire un'interfaccia funzionale (= con un metodo astratto)
  • p1 e f1 funzionano bene, inferendo ciascuno un tipo diverso
  • obj2 getta una Predicateper Object- sciocco, ma valida
  • f2 non riesce in fase di esecuzione - non puoi lanciare Predicatea Function, non è più circa l'inferenza
  • f3 funziona: tu chiami il metodo del predicato testdefinito dalla sua lambda
  • p2 non si compila - Integernon ha isEmptymetodo
  • Anche p3 non viene compilato - non esiste String::isEmptyun metodo statico con Integerargomento

Spero che questo aiuti ad avere maggiori informazioni su come funziona l'inferenza del tipo.


46

Basandosi sulle risposte degli altri e sull'esperienza personale:

Predicate<String> blank = String::isEmpty;
content.stream()
       .filter(blank.negate())

4
Interessante: non è possibile incorporare il ::riferimento funzionale come si potrebbe desiderare ( String::isEmpty.negate()), ma se si assegna a una variabile per prima (o si esegue il cast per Predicate<String>primo), funziona. Penso che lambda w / !sarà più leggibile nella maggior parte dei casi, ma è utile sapere cosa può e non può essere compilato.
Joshua Goldberg,

2
@JoshuaGoldberg Ho spiegato che nella mia risposta: il riferimento al metodo non è un Predicato da solo. Qui, il casting viene eseguito dalla variabile.
Vlasec,

17

Un'altra opzione è quella di utilizzare il casting lambda in contesti non ambigui in una 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 quindi importare staticamente la classe di utilità:

stream.filter(as(String::isEmpty).negate())

1
In realtà sono sorpreso che funzioni, ma sembra che JDK preferisca il Predicato <T> rispetto alla Funzione <T, Booleano>. Ma Lambdas non lancerà nulla sulla funzione <T, booleana>.
Vlasec,

Funziona con String ma non con List: Error: (20, 39) java: riferimento a as è ambiguo sia il metodo <T> che (java.util.function.Consumer <T>) in com.strands.sbs.function. Lambdas e metodo <T, R> as (java.util.function.Function <T, R>) in com.strands.sbs.function.Lambdas match
Daniel Pinyol

Daniel, potrebbe succedere se stai cercando di usare il metodo sovraccarico :)
Askar Kalykov

Ora che capisco l'inferenza del tipo molto meglio di quanto originariamente, capisco come funziona. Fondamentalmente, trova solo l'unica opzione che funziona. Sembra interessante, semplicemente non so se esiste un nome migliore che non causi il boilerplate.
Vlasec,

12

Non dovrebbe Predicate#negateessere quello che stai cercando?


Devi avere un Predicateprimo.
Sotirios Delimanolis,

21
Dovete fusione String::isEmpty()di Predicate<String>prima - è molto brutto.
axtavt,

3
@assylias Usa come Predicate<String> p = (Predicate<String>) String::isEmpty;ep.negate() .
Sotirios Delimanolis,

8
@SotiriosDelimanolis Lo so, ma questo sconfigge lo scopo - Preferirei scrivere s -> !s.isEmpty()in quel caso!
assylias

@assylias: Sì, credo che questa sia l'idea; che semplicemente scrivere il lambda longhand è il fallback previsto.
Louis Wasserman,

8

In questo caso è possibile utilizzare org.apache.commons.lang3.StringUtilse fare

int nonEmptyStrings = s.filter(StringUtils::isNotEmpty).count();

6
No. La domanda è come negare qualsiasi riferimento al metodo e prende String::isEmptycome esempio. Sono ancora informazioni pertinenti se si dispone di questo caso d'uso, ma se risponde solo al caso d'uso String, non dovrebbe essere accettato.
Anthony Drogon,

4

Ho scritto una classe di utilità completa (ispirata alla proposta di Askar) che può prendere l'espressione lambda di Java 8 e trasformarli (se applicabile) in qualsiasi lambda standard Java 8 tipizzata definita nel pacchetto java.util.function. Ad esempio puoi fare:

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

Perché ci sarebbero numerose ambiguità se tutti i metodi statici fossero chiamati giusti as() , ho optato per chiamare il metodo "come" seguito dal tipo restituito. Questo ci dà il pieno controllo dell'interpretazione lambda. Di seguito è la prima parte della classe di utilità (piuttosto grande) che rivela il modello utilizzato.

Dai un'occhiata alla classe completa qui (su 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...
}

4

È possibile utilizzare Predicati dalle raccolte Eclipse

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

Se non puoi cambiare le stringhe da List:

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

Se hai solo bisogno di una negazione String.isEmpty()puoi anche usareStringPredicates.notEmpty() .

Nota: sono un collaboratore delle raccolte Eclipse.



0

Se stai usando Spring Boot (2.0.0+) puoi usare:

import org.springframework.util.StringUtils;

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

Che fa: return (str != null && !str.isEmpty());

Quindi avrà l'effetto di negazione richiesto per isEmpty

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.