Ein Prädikat ist eine Aussage über einen Gegenstand, die wahr oder falsch. Die Frage mit Character.isDigit(‚a‘), ob das Zeichen „a“ eine Ziffer ist, wird mit falsch beantwortet – isDigit ist also ein Prädikat, weil es über einen Gegenstand, einem Zeichen, eine Wahrheitsaussage fällen kann.
Flexibler sind Prädikate, wenn sie als Objekte repräsentiert werden, weil sie dann an unterschiedliche Stellen weitergegeben werden können, wenn etwa über ein Prädikat bestimmt, was aus einer Sammlung gelöscht werden soll oder ob mindestens ein Element in einer Sammlung ist, was ein Prädikat erfüllt.
Das java.util.function-Paket deklariert eine flexible funktionale Schnittstelle Predicate auf folgende Weise:
interface java.util.function.Predicate<T>
* boolean test(T t)
Führt einen Test auf t durch und liefert true, wenn das Kriterium erfüllt ist.
Beispiel
Der Test, ob ein Zeichen eine Ziffer ist, kann durch Prädikat-Objekte nun auch anders durchgeführt werden:
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
System.out.println( isDigit.test(‚a‘) ); // false
Hätte es die Schnittstelle Predicate schon früher in Java 1.0 gegeben, hätte es einer der Methode Character.isDigit(…) gar nicht bedurft, es hätte auch ein Predicate als statische Variable in Character geben können, so dass ein Test dann geschrieben würde als Character.IS_DIGIT.test(…) oder als Rückgabe von einer Methode isDigit(), mit der Nutzung Character.isDigit().test(…). Es ist daher gut möglich, dass sich in Zukunft die API dahingehend verändert, dass Aussagen auf Gegenständen mit Wahrheitsrückgabe nicht mehr als Methoden bei den Klassen realisiert werden, sondern als Prädikat-Objekte angeboten werden. Aber Methoden-Referenzen geben zum Glück die Flexibilität, dass problemlos Methoden als Lambda-Ausdrücke genützt werden können und so kommen wir wieder von Methoden zu Funktionen.
Typ Predicate in der API
Es gibt in der Java-API vier Stellen, an denen Prediate-Objekte genutzt werden:
· Als Argument für Lösch-Methoden, um in Sammlungen Elemente zu spezifizieren, die gelöscht werden sollen.
· Bei den Default-Methoden der Predicate-Schnittstelle selbst, um Prädikate zu verknüpfen.
· Bei regulären Ausdrücken, um ein Pattern als Predicate nutzen zu können.
· In der Stream-API, bei der Objekte beim Durchlaufen des Stroms über ein Prädikat identifiziert werden, um sie etwa auszufiltern.
Beispiel
Lösche aus einer Liste mit Zeichen alle die, die Ziffern sind (es bleiben nur Zeichen übrig, etwa Buchstaben).
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );
list.removeIf( isDigit );
Default-Methoden von Predicate
Es gibt eine Reihe von Default-Methoden, die die funktionale Schnittstelle Predicate anbietet. Zusammenfassend:
interface java.util.function.Predicate<T>
default Predicate<T> negate()
default Predicate<T> and(Predicate<? super T> p)
default Predicate<T> or(Predicate<? super T> p)
default Predicate<T> xor(Predicate<? super T> p)
Die Methodennamen sprechen für sich.
Beispiel
Lösche aus einer Liste mit Zeichen alle die, die keine Ziffern sind.
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
Predicate<Character> isNotDigit = isDigit.negate();
List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );
list.removeIf( isNotDigit );
>> Character.IS_DIGIT.test(..)
Solche static Instanzen würden permanent Memory verbrauchen
>> Rückgabe von einer Methode isDigit()
Das verwenden von „new“ ist langsam
Der Hotspot darf in beiden Fällen nicht mal optimieren, selbst wenn
die Implementierung vom Predicate stateless ist,
im Gegensatz zu einer (stateless) static Methode.
Gerade bei solchen Methoden welche u.U. x Millionenfach aufgerufen werden
ist die Wahl dies als static Methode zu realisieren nicht die schlechteste,
was jetzt nicht heisst, dass in diesem konkreten Fall die Implementierung
von isDigit intelligent ist.
Grundsätzlich macht das Predicate Interface schon Sinn,
ihr Beispiel ist einfach unglücklich gewählt.