Jak zanegować predykat odwołania do metody


330

W Javie 8 możesz użyć odwołania do metody do filtrowania strumienia, na przykład:

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

Czy istnieje sposób na utworzenie odwołania do metody, które jest negacją istniejącego, tj. Coś takiego:

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

Mogłem stworzyć notmetodę jak poniżej, ale zastanawiałem się, czy JDK oferuje coś podobnego.

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

6
JDK-8050818 obejmuje dodanie Predicate.not(Predicate)metody statycznej . Ale ten problem jest nadal otwarty, więc zobaczymy to najwcześniej w Javie 12 (jeśli w ogóle).
Stefan Zobel

1
Wygląda na to, że ta odpowiedź może być również najlepszym rozwiązaniem zaadaptowanym w JDK / 11.
Naman

2
Naprawdę chciałbym zobaczyć specjalną składnię odwołania do metody dla tego przypadku: s.filter (String ::! IsEmpty)
Mike Twain

Odpowiedzi:


177

Predicate.not( … )

oferuje nową metodę Predicate # not

Możesz więc zanegować odwołanie do metody:

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

214

Planuję zaimportować statycznie następujące elementy, aby umożliwić użycie odwołania do metody w wierszu:

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

na przykład

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

Aktualizacja : Począwszy od Java-11, JDK oferuje równieżwbudowane podobne rozwiązanie .


9
@SaintHill, ale musisz to zapisać, nadając parametrowi nazwę
flup



149

Istnieje sposób na skomponowanie odwołania do metody, które jest przeciwieństwem bieżącego odwołania do metody. Zobacz odpowiedź @ vlasec poniżej, która pokazuje, jak jawnie rzutować odwołanie do metody na a, Predicatea następnie przekonwertować ją za pomocą negatefunkcji. To jeden ze sposobów na kilka innych niezbyt kłopotliwych sposobów.

Przeciwieństwo tego:

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

czy to jest:

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

albo to:

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

Osobiście wolę późniejszą technikę, ponieważ uważam, że czytanie it -> !it.isEmpty()jest łatwiejsze niż długa pełna wypowiedź, a następnie negacja.

Można również zrobić predykat i użyć go ponownie:

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

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

Lub, jeśli masz kolekcję lub tablicę, po prostu użyj pętli for, która jest prosta, ma mniejszy narzut i * może być ** szybsza:

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

* Jeśli chcesz wiedzieć, co jest szybsze, użyj JMH http://openjdk.java.net/projects/code-tools/jmh i unikaj ręcznego kodu testowego, chyba że unika się wszystkich optymalizacji JVM - patrz Java 8: wydajność strumieni vs Kolekcje

** Dostaję błąd, sugerując, że technika for-loop jest szybsza. Eliminuje tworzenie strumienia, eliminuje użycie innego wywołania metody (funkcja ujemna dla predykatu) i eliminuje tymczasową listę / licznik akumulatorów. Kilka rzeczy, które są zapisywane przez ostatni konstrukt, które mogą go przyspieszyć.

Myślę, że jest to jednak prostsze i przyjemniejsze, nawet jeśli nie szybsze. Jeśli praca wymaga młota i gwoździa, nie przynoś piły łańcuchowej i kleju! Wiem, że niektórzy z was mają z tym problem.

lista życzeń: Chciałbym, aby Streamfunkcje Java ewoluowały nieco teraz, gdy użytkownicy Java są z nimi bardziej zaznajomieni. Na przykład metoda „count” w Streamie może zaakceptować a, Predicatedzięki czemu można to zrobić bezpośrednio w następujący sposób:

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

or

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

Dlaczego mówisz, że jest znacznie szybszy ?
José Andias

@ JoséAndias (1) Czy jest to szybsze czy „dużo szybsze”? (2) Jeśli tak, dlaczego? Co ustaliłeś
Koordynator

3
Proszę o rozwinięcie „dużo szybszego uruchomienia”. Pytania: (1) Czy jest to szybsze czy „dużo szybsze”? (2) Jeśli tak, dlaczego? Co ustaliłeś lepiej na nie odpowie autor, autor oświadczenia. Nie uważam, aby było to szybsze lub wolniejsze. Dziękuję
José Andias

2
Następnie wyrzucę to do rozważenia - eliminuje tworzenie strumienia, eliminuje użycie innego wywołania metody (funkcja ujemna dla predykatu) i eliminuje tymczasową listę / licznik akumulatorów. A więc kilka rzeczy, które zostały zapisane przez ostatni konstrukt. Nie jestem pewien, czy to jest szybsze, ani o ile szybsze, ale zakładam, że jest „dużo” szybsze. Ale może „dużo” jest subiektywne. Łatwiej jest kodować później niż tworzyć negatywne predykaty i strumienie, aby wykonać proste liczenie. Moja preferencja .
Koordynator

4
negate () wydaje się idealnym rozwiązaniem. Szkoda, że ​​nie jest statyczny, jak Predicate.negate(String::isEmpty);bez uciążliwego rzucania.
Joel Shemtov

91

Predicateposiada metody and, ori negate.

Jednak String::isEmptynie jest Predicate, to po prostu String -> Booleanlambda i wciąż może stać się czymkolwiek, np Function<String, Boolean>. Wnioskowanie typu jest tym, co musi się zdarzyć najpierw. W filterwyprowadza metoda wpisać niejawnie . Ale jeśli zaprzeczysz temu, zanim przekażesz go jako argument, to już się nie stanie. Jak wspomniano @axtavt, jawnego wnioskowania można użyć jako brzydkiego sposobu:

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

Istnieją inne sposoby wskazane w innych odpowiedziach, przy czym notmetoda statyczna i lambda są prawdopodobnie najlepszymi pomysłami. To kończy sekcję tl; dr .


Jeśli jednak chcesz głębiej zrozumieć wnioskowanie o typie lambda, chciałbym wyjaśnić to bardziej szczegółowo, używając przykładów. Spójrz na nie i spróbuj dowiedzieć się, co się stanie:

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 się nie kompiluje - lambdy muszą wnioskować o interfejsie funkcjonalnym (= za pomocą jednej metody abstrakcyjnej)
  • p1 i f1 działają dobrze, z których każdy zakłada inny typ
  • obj2 rzutuje Predicatena Objectgłupie, ale poprawne
  • F2 nie przy starcie - nie można rzucać Predicatesię Function, to już nie chodzi o wnioskowanie
  • f3 działa - wywołujesz metodę predykatu testzdefiniowaną przez jej lambda
  • p2 nie kompiluje się - Integernie ma isEmptymetody
  • p3 też się nie kompiluje - nie ma String::isEmptymetody statycznej z Integerargumentem

Mam nadzieję, że pomoże to uzyskać lepszy wgląd w działanie wnioskowania typu.


45

Opierając się na odpowiedziach innych i osobistym doświadczeniu:

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

4
Ciekawe - nie możesz wstawiać funkcjonalnego ::odwołania, jak String::isEmpty.negate()byś chciał ( ), ale jeśli najpierw przypiszesz zmienną (lub przerzucisz na Predicate<String>pierwszą), to zadziała. Myślę, że lambda w / !będzie najbardziej czytelna w większości przypadków, ale warto wiedzieć, co można, a czego nie można skompilować.
Joshua Goldberg

2
@JoshuaGoldberg Wyjaśniłem, że w mojej odpowiedzi: odwołanie do metody nie jest samo w sobie predykatem. Tutaj rzutowanie odbywa się przez zmienną.
Vlasec

17

Inną opcją jest wykorzystanie rzutowania lambda w niejednoznacznych kontekstach do jednej klasy:

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;
    }

}

... a następnie zaimportuj statycznie klasę narzędzia:

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

1
Jestem właściwie zaskoczony, że to działa - ale wydaje się, że JDK woli Predicate <T> niż Function <T, Boolean>. Ale nie sprawisz, że Lambdas rzuci cokolwiek na Function <T, Boolean>.
Vlasec

Działa dla String, ale nie dla List: Błąd: (20, 39) java: odwołanie do as jest niejednoznaczne dla obu metod <T> as (java.util.function.Consumer <T>) w com.strands.sbs.function. Lambdas i metoda <T, R> as (java.util.function.Function <T, R>) w com.strands.sbs.function. Lambdas match
Daniel Pinyol

Daniel, może się zdarzyć, jeśli spróbujesz użyć metody przeciążonej :)
Askar Kalykov

Teraz, gdy rozumiem wnioskowanie typu znacznie lepiej niż początkowo, rozumiem, jak to działa. Zasadniczo znajduje tylko jedyną opcję, która działa. Wygląda interesująco, po prostu nie wiem, czy jest jakaś lepsza nazwa, która nie powoduje bojlera.
Vlasec,

12

Czy nie powinno Predicate#negatebyć to, czego szukasz?


Musisz zdobyć Predicatepierwszy.
Sotirios Delimanolis

21
Trzeba obsady String::isEmpty()do Predicate<String>wcześniej - to jest bardzo brzydka.
axtavt

3
@assylias Użyj jako Predicate<String> p = (Predicate<String>) String::isEmpty;i p.negate().
Sotirios Delimanolis

8
@SotiriosDelimanolis Wiem, ale to przeczy celowi - wolałbym pisać s -> !s.isEmpty()w takim przypadku!
assylias

@assylias: Tak, wydaje mi się, że taki jest właśnie pomysł; że po prostu napisanie długiej dłoni lambda jest zamierzonym rozwiązaniem zastępczym.
Louis Wasserman

8

W takim przypadku org.apache.commons.lang3.StringUtilsmożesz użyć i zrobić

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

6
Nie. Pytanie brzmi: jak zanegować odniesienie do dowolnej metody i String::isEmptystanowi przykład. Jest to nadal ważna informacja, jeśli masz ten przypadek użycia, ale jeśli odpowiada on tylko na przypadek użycia Ciąg, to nie powinien zostać zaakceptowany.
Anthony Drogon,

4

Napisałem kompletną klasę narzędziową (zainspirowaną propozycją Askara), która może pobrać wyrażenie lambda Java 8 i zamienić je (jeśli dotyczy) w dowolny typowy standardowy lambda Java 8 zdefiniowany w pakiecie java.util.function. Możesz na przykład:

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

Ponieważ byłoby wiele dwuznaczności, gdyby wszystkie statyczne metody zostały nazwane tylko as(), zdecydowałem się wywołać metodę „as”, a następnie zwracany typ. To daje nam pełną kontrolę nad interpretacją lambda. Poniżej znajduje się pierwsza część (nieco dużej) klasy użyteczności ujawniająca zastosowany wzorzec.

Spójrz na całą klasę tutaj (w skrócie).

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

Możesz użyć predykatów z kolekcji Eclipse

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

Jeśli nie możesz zmienić ciągów z List:

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

Jeśli potrzebujesz tylko negacji String.isEmpty(), możesz również użyć StringPredicates.notEmpty().

Uwaga: Jestem współtwórcą kolekcji Eclipse.



0

Jeśli używasz Spring Boot (2.0.0+), możesz użyć:

import org.springframework.util.StringUtils;

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

Co robi: return (str != null && !str.isEmpty());

Będzie to miało wymagany efekt negacji isEmpty

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.