So negieren Sie ein Methodenreferenzprädikat


330

In Java 8 können Sie eine Methodenreferenz verwenden, um einen Stream zu filtern, zum Beispiel:

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

Gibt es eine Möglichkeit, eine Methodenreferenz zu erstellen, die die Negation einer vorhandenen ist, dh so etwas wie:

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

Ich könnte die notMethode wie unten erstellen , habe mich aber gefragt, ob das JDK etwas Ähnliches anbietet.

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

6
JDK-8050818 behandelt das Hinzufügen einer statischen Predicate.not(Predicate)Methode. Dieses Problem ist jedoch noch offen, sodass wir es frühestens in Java 12 sehen werden (falls überhaupt).
Stefan Zobel

1
Diese Antwort scheint die ultimative Lösung zu sein, die auch in JDK / 11 angepasst wurde.
Naman

2
Ich würde wirklich gerne eine spezielle Methodenreferenzsyntax für diesen Fall sehen: s.filter (String ::! IsEmpty)
Mike Twain

Antworten:


176

Predicate.not( … )

bietet eine neue Methode Prädikat # nicht

So können Sie die Methodenreferenz negieren:

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

214

Ich plane, Folgendes statisch zu importieren, damit die Methodenreferenz inline verwendet werden kann:

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

z.B

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

Update : Ab Java-11 bietet das JDK auch eine ähnliche integrierte Lösung .


9
@ SaintHill, aber dann müssen Sie es ausschreiben und dem Parameter einen Namen geben
flup



149

Es gibt eine Möglichkeit, eine Methodenreferenz zu erstellen, die das Gegenteil einer aktuellen Methodenreferenz ist. Die Antwort von @ vlasec unten zeigt, wie Sie die Methodenreferenz explizit in a Predicateumwandeln und sie dann mithilfe der negateFunktion konvertieren . Das ist ein Weg unter ein paar anderen, nicht allzu mühsamen Wegen, dies zu tun.

Das Gegenteil davon:

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

ist das:

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

oder dieses:

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

Persönlich bevorzuge ich die spätere Technik, weil ich es klarer finde, zu lesen it -> !it.isEmpty()als eine lange ausführliche explizite Besetzung und dann zu negieren.

Man könnte auch ein Prädikat erstellen und es wiederverwenden:

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

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

Wenn Sie über eine Sammlung oder ein Array verfügen, verwenden Sie einfach eine for-Schleife, die einfach ist, weniger Overhead hat und * möglicherweise ** schneller ist:

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

* Wenn Sie wissen möchten, was schneller ist, verwenden Sie JMH http://openjdk.java.net/projects/code-tools/jmh und vermeiden Sie Hand-Benchmark-Code, es sei denn, es vermeidet alle JVM-Optimierungen - siehe Java 8: Leistung von Streams vs Sammlungen

** Ich bekomme Flak, weil ich behaupte, dass die For-Loop-Technik schneller ist. Es eliminiert eine Stream-Erstellung, es eliminiert die Verwendung eines anderen Methodenaufrufs (negative Funktion für Prädikat) und es eliminiert eine temporäre Akkumulatorliste / Zähler. Also ein paar Dinge, die vom letzten Konstrukt gespeichert werden und die es möglicherweise schneller machen.

Ich denke, es ist einfacher und schöner, wenn auch nicht schneller. Wenn der Job einen Hammer und einen Nagel erfordert, bringen Sie keine Kettensäge und keinen Kleber mit! Ich weiß, dass einige von Ihnen Probleme damit haben.

Wunschliste: Ich würde gerne sehen, dass sich die Java- StreamFunktionen etwas weiterentwickeln, da Java-Benutzer mit ihnen besser vertraut sind. Zum Beispiel könnte die 'count'-Methode in Stream a akzeptieren, Predicateso dass dies direkt wie folgt erfolgen kann:

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

or

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

Warum sagst du, ist es viel schneller ?
José Andias

@ JoséAndias (1) Ist es schneller oder "viel schneller"? (2) Wenn ja, warum? Was hast du bestimmt?
Der Koordinator

3
Ich bitte Sie, näher auf "viel schneller zu laufen" einzugehen. Die Fragen: (1) Ist es schneller oder "viel schneller"? (2) Wenn ja, warum? Was hast du bestimmt? werden besser von Ihnen, dem Autor der Erklärung, beantwortet. Ich halte es nicht für schneller oder langsamer. Vielen Dank
José Andias

2
Dann werde ich dies für Ihre Überlegung wegwerfen - Es eliminiert eine Stream-Erstellung, es eliminiert die Verwendung eines anderen Methodenaufrufs (negative Funktion für Prädikat) und es eliminiert eine temporäre Akkumulatorliste / Zähler. Also ein paar Dinge, die vom letzten Konstrukt gespeichert werden. Ich bin mir nicht sicher, ob es schneller ist oder wie viel schneller, aber ich gehe davon aus, dass es "viel" schneller ist. Aber vielleicht ist "viel" subjektiv. Es ist einfacher, das spätere zu codieren, als negative Prädikate und Streams zu erstellen, um eine gerade Zählung durchzuführen. Meine Vorliebe .
Der Koordinator

4
negate () scheint eine ideale Lösung zu sein. Schade, dass es nicht statisch ist wie Predicate.negate(String::isEmpty);ohne das umständliche Casting.
Joel Shemtov

91

Predicatehat Methoden and, orund negate.

Ist String::isEmptyjedoch kein Predicate, es ist nur ein String -> BooleanLambda und es könnte immer noch alles werden, z Function<String, Boolean>. Typinferenz ist das, was zuerst passieren muss. Die filterMethode leitet den Typ implizit ab . Wenn Sie es jedoch negieren, bevor Sie es als Argument übergeben, geschieht dies nicht mehr. Wie @axtavt erwähnte, kann explizite Folgerung als hässlicher Weg verwendet werden:

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

In anderen Antworten werden andere Möglichkeiten empfohlen, wobei statische notMethode und Lambda höchstwahrscheinlich die besten Ideen sind. Dies schließt die tl; dr Abschnitt.


Wenn Sie jedoch ein tieferes Verständnis der Inferenz vom Lambda-Typ wünschen, möchte ich dies anhand von Beispielen etwas ausführlicher erläutern. Schauen Sie sich diese an und versuchen Sie herauszufinden, was passiert:

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 kompiliert nicht - Lambdas müssen auf eine funktionale Schnittstelle schließen (= mit einer abstrakten Methode)
  • p1 und f1 funktionieren einwandfrei und schließen jeweils auf einen anderen Typ ab
  • obj2 wirft ein Predicatezu Objectdummes, aber gültiges
  • f2 nicht zur Laufzeit - man kann nicht werfen Predicatezu Function, es geht nicht mehr um Inferenz
  • f3 funktioniert - Sie rufen die Methode des Prädikats auf test, die durch sein Lambda definiert ist
  • p2 kompiliert nicht - Integerhat keine isEmptyMethode
  • p3 wird auch nicht kompiliert - es gibt keine String::isEmptystatische Methode mit IntegerArgument

Ich hoffe, dies hilft, mehr Einblick in die Funktionsweise der Typinferenz zu bekommen.


45

Aufbauend auf den Antworten und persönlichen Erfahrungen anderer:

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

4
Interessant - Sie können die Funktionsreferenz nicht ::wie gewünscht inline einfügen ( String::isEmpty.negate()), aber wenn Sie zuerst einer Variablen zuweisen (oder zuerst umwandeln Predicate<String>), funktioniert dies. Ich denke, Lambda !wird in den meisten Fällen am besten lesbar sein, aber es ist hilfreich zu wissen, was kompiliert werden kann und was nicht.
Joshua Goldberg

2
@JoshuaGoldberg Ich habe das in meiner Antwort erklärt: Die Methodenreferenz ist kein Prädikat für sich. Hier erfolgt das Casting durch die Variable.
Vlasec

17

Eine andere Möglichkeit besteht darin, Lambda-Casting in nicht mehrdeutigen Kontexten in einer Klasse zu verwenden:

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

}

... und dann statisch die Utility-Klasse importieren:

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

1
Ich bin tatsächlich überrascht, dass dies funktioniert - aber es scheint, dass JDK Predicate <T> gegenüber Function <T, Boolean> bevorzugt. Aber Sie werden Lambdas nicht dazu bringen, etwas in Funktion <T, Boolean> umzuwandeln.
Vlasec

Es funktioniert für String, aber nicht für List: Error: (20, 39) java: Verweis auf as ist mehrdeutig, beide Methoden <T> als (java.util.function.Consumer <T>) in com.strands.sbs.function. Lambdas und Methode <T, R> als (java.util.function.Function <T, R>) in com.strands.sbs.function.Lambdas stimmen überein
Daniel Pinyol

Daniel, es könnte passieren, wenn Sie versuchen, überladene Methode zu verwenden :)
Askar Kalykov

Jetzt, da ich die Typinferenz viel besser verstehe als ursprünglich, verstehe ich, wie sie funktioniert. Grundsätzlich findet es nur die einzige Option, die funktioniert. Sieht interessant aus, ich weiß nur nicht, ob es einen besseren Namen gibt, der kein Boilerplate verursacht.
Vlasec

12

Sollte nicht das Predicate#negatesein, wonach Sie suchen?


Sie müssen eine Predicateerste bekommen.
Sotirios Delimanolis

21
Sie müssen Guss String::isEmpty()zu Predicate<String>vor - es ist sehr hässlich.
Axtavt

3
@assylias Als Predicate<String> p = (Predicate<String>) String::isEmpty;und verwenden p.negate().
Sotirios Delimanolis

8
@SotiriosDelimanolis Ich weiß, aber das macht den Zweck zunichte - ich würde s -> !s.isEmpty()in diesem Fall lieber schreiben !
Assylias

@assylias: Ja, ich glaube das ist eigentlich die Idee; Das bloße Ausschreiben der Lambda-Langschrift ist der beabsichtigte Rückfall.
Louis Wasserman

8

In diesem Fall könnten Sie das org.apache.commons.lang3.StringUtilsund tun

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

6
Nein. Die Frage ist, wie eine Methodenreferenz negiert werden kann, und dient String::isEmptyals Beispiel. Es sind immer noch relevante Informationen, wenn Sie diesen Anwendungsfall haben, aber wenn er nur den String-Anwendungsfall beantwortet, sollte er nicht akzeptiert werden.
Anthony Drogon

4

Ich habe eine vollständige Utility-Klasse geschrieben (inspiriert von Askars Vorschlag), die Java 8-Lambda-Ausdrücke verwenden und sie (falls zutreffend) in jedes im Paket definierte typisierte Standard-Java 8-Lambda umwandeln kann java.util.function. Sie können zum Beispiel Folgendes tun:

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

Da es zahlreiche Unklarheiten geben würde, wenn alle statischen Methoden nur benannt würden as(), habe ich mich dafür entschieden, die Methode "as" gefolgt vom zurückgegebenen Typ aufzurufen. Dies gibt uns die volle Kontrolle über die Lambda-Interpretation. Unten ist der erste Teil der (etwas großen) Utility-Klasse, der das verwendete Muster enthüllt.

Schauen Sie sich hier die gesamte Klasse an (im Kern).

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

Sie können Prädikate aus Eclipse-Sammlungen verwenden

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

Wenn Sie die Zeichenfolgen nicht ändern können von List:

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

Wenn Sie nur eine Negation von benötigen String.isEmpty(), können Sie auch verwenden StringPredicates.notEmpty().

Hinweis: Ich bin ein Mitarbeiter von Eclipse Collections.



0

Wenn Sie Spring Boot (2.0.0+) verwenden, können Sie Folgendes verwenden:

import org.springframework.util.StringUtils;

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

Welches tut: return (str != null && !str.isEmpty());

Es wird also den erforderlichen Negationseffekt für haben isEmpty

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.