W3docs

Interfaccia Predicate di Java

Testa condizioni sui valori in Java con l'interfaccia funzionale Predicate e i suoi combinatori and/or/negate.

Predicate<T> è l'interfaccia funzionale per la domanda "questo valore va bene?" — un input di tipo T, una risposta boolean. Si trova al cuore di Stream.filter, Collection.removeIf, Optional.filter e di ogni metodo del JDK che dice "mantieni quelli che corrispondono." L'interfaccia è minima — un unico metodo test(T) — ma viene fornita con una piccola algebra di combinatori (and, or, negate, isEqual, not) che consente di costruire condizioni complesse a partire da quelle semplici, senza mai scrivere manualmente la logica booleana di collegamento.

Questo capitolo segue la stessa struttura degli altri approfondimenti sulle interfacce della Parte 12: l'interfaccia, i suoi tre o quattro metodi utili, l'algebra, e poi un esempio pratico.

L'interfaccia

L'intera dichiarazione, in forma semplificata:

@FunctionalInterface
public interface Predicate<T> {
  boolean test(T t);                         // the only abstract method

  default Predicate<T> and(Predicate<? super T> other);
  default Predicate<T> or(Predicate<? super T> other);
  default Predicate<T> negate();
  static  <T> Predicate<T> isEqual(Object target);
  static  <T> Predicate<T> not(Predicate<? super T> target);   // Java 11+
}

test è l'unico metodo astratto che lambda e riferimenti a metodi implementano. Tutto il resto è costruito sopra di esso. Raramente chiamerai test direttamente — stream().filter(...) e list.removeIf(...) lo chiamano per te — ma conoscere il nome del metodo è importante quando scrivi codice che accetta un Predicate<T> e deve invocarlo.

Predicate<String> notBlank = s -> !s.isBlank();
boolean ok = notBlank.test("hello");          // true

and, or, negate — algebra booleana senza il codice di collegamento

I tre metodi default compongono i predicati allo stesso modo in cui gli operatori &&, ||, ! compongono i booleani:

Predicate<String> notNull  = Objects::nonNull;
Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> longEnough = s -> s.length() >= 3;

Predicate<String> useful   = notNull.and(notBlank).and(longEnough);
Predicate<String> usableOrShort = useful.or(s -> s.length() == 1);
Predicate<String> bad      = useful.negate();

Due proprietà sono importanti:

  • Cortocircuito, nell'ordine di dichiarazione. a.and(b) chiama b.test solo quando a.test ha restituito true. a.or(b) chiama b.test solo quando a.test ha restituito false. È lo stesso ordine di valutazione di && e ||, il che significa che puoi mettere prima i controlli economici e a frequente fallimento, e quelli costosi alla fine.
  • Ogni chiamata restituisce un nuovo Predicate. I combinatori non mutano this. Riutilizza gli originali quanto vuoi.

negate() inverte semplicemente il risultato. useful.negate() restituisce true per null, valori vuoti e stringhe più corte di 3 — ogni caso che useful aveva rifiutato.

Predicate.not — la negazione leggibile

Java 11 ha aggiunto una scorciatoia statica:

list.removeIf(Predicate.not(String::isBlank));    // remove every blank string

Predicate.not(p) fornisce la stessa risposta boolean di p.negate(), ma si compone in modo molto più naturale nel punto di chiamata. La forma con riferimento a metodo String::isBlank è già un Predicate<String> — ma non puoi scrivere (String::isBlank).negate(), perché il compilatore ha bisogno di un tipo target prima di poter risolvere il riferimento. Predicate.not(String::isBlank) gli fornisce quel tipo target, e il tutto si legge come "non blank" nell'ordine naturale.

Un import statico di Predicate.not rende le catene di filtri ancora più pulite:

import static java.util.function.Predicate.not;
...
var nonBlank = lines.stream().filter(not(String::isBlank)).toList();

Predicate.isEqual — uguaglianza null-safe

Predicate<Object> isFoo = Predicate.isEqual("foo");        // o -> Objects.equals(o, "foo")

L'implementazione è letteralmente t -> Objects.equals(target, t), il che significa che null su entrambi i lati viene confrontato in modo sicuro. Raramente risparmia battute rispetto a s -> s.equals("foo"), ma ti salva quando lo stream potrebbe contenere nullnull.equals("foo") lancerebbe una NPE, mentre Objects.equals(null, "foo") restituisce false.

Dove appare Predicate<T> nel JDK

Lo stesso Predicate<T> scorre attraverso ogni API di "filtraggio":

Stream<String> kept = stream.filter(notBlank);             // Stream.filter
boolean removed     = list.removeIf(String::isBlank);      // Collection.removeIf
Optional<String> ok = opt.filter(notBlank);                // Optional.filter
boolean any         = stream.anyMatch(notBlank);           // anyMatch / allMatch / noneMatch
map.values().removeIf(String::isBlank);                    // Map view + Collection.removeIf

Ognuno di questi ha la stessa forma, quindi un Predicate<T> costruito una volta è riutilizzabile in ogni direzione — e assemblarlo con and/or/negate è esattamente il modo per evitare il problema dei "tre filtri leggermente diversi, tutti quasi duplicati."

Specializzazioni primitive — IntPredicate, LongPredicate, DoublePredicate

Predicate<Integer> funziona con gli int, ma ogni chiamata esegue il boxing dell'input. Per pipeline numeriche intensive il pacchetto include:

IntPredicate    even = n -> n % 2 == 0;
LongPredicate   big  = n -> n > 1_000_000_000L;
DoublePredicate hot  = d -> d > 37.5;

Stessa algebra and/or/negate, nessun boxing. Queste sono ciò che IntStream.filter accetta — usare Predicate<Integer> lì costringerebbe lo stream a fare l'autoboxing di ogni elemento in ingresso.

BiPredicate<T, U> — test a due argomenti

Quando la domanda richiede due input (una chiave e un valore, una riga e una colonna, un vecchio e un nuovo), usa BiPredicate:

BiPredicate<String, Integer> longEnoughFor = (s, n) -> s.length() >= n;
boolean ok = longEnoughFor.test("hello", 4);             // true

La superficie dei combinatori è più ridotta — and, or, negate esistono, ma non c'è un isEqual o un not a due argomenti. Map.removeIf((k, v) -> ...) è esattamente un BiPredicate<K, V>.

Un esempio pratico: predicati, composizione, l'algebra e dove si collegano

Il programma seguente costruisce tre semplici predicati su User, li compone con and/or/negate, dimostra il cortocircuito contando le chiamate, sostituisce Predicate.not per la negazione in un punto di chiamata removeIf, e usa un IntPredicate con un IntStream per mostrare la variante primitiva.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • I tre predicati di base (adult, active, namedWell) sono rimasti riutilizzabili. eligible, minor e reachable sono stati costruiti per composizione anziché scrivendo tre lambda separate con logica sovrapposta.
  • and ha cortocircuitato esattamente come fa &&: expensive è stato eseguito meno volte di cheap perché ogni minore veniva rifiutato prima che scattasse il controllo costoso. Questo è il meccanismo di ottimizzazione disponibile attraverso l'ordine — metti prima i controlli economici e a frequente fallimento.
  • Predicate.not(...) nel punto di chiamata removeIf si leggeva come inglese semplice ("remove if not non-blank") ed evitava la necessità di un tipo target prima della negazione. L'import statico di not è il piccolo tocco finale.
  • Predicate.isEqual("foo") ha contato le due voci "foo" passando oltre un null senza lanciare eccezioni. s -> s.equals("foo") avrebbe generato una NPE sull'elemento null.
  • IntPredicate even = n -> n % 2 == 0; si è collegato direttamente a IntStream.filter senza boxing — e lo stesso combinatore .and(...) funziona sulla specializzazione primitiva.

Cosa viene dopo

Predicate<T> risponde sì o no. Il capitolo successivo, Interfaccia Function di Java, tratta l'interfaccia per l'altra metà del lavoro con gli stream: trasformare un valore in un altro. La forma — metodo singolo, composizione con metodi default (andThen, compose, più lo statico identity()) — è la stessa di Predicate, e le stesse lezioni sull'ordine, il riutilizzo e le specializzazioni primitive si applicano anche qui.

Esercitati

Pratica
Hai `Predicate<String> notNull = Objects::nonNull;` e `Predicate<String> notBlank = s -> !s.isBlank();` e vuoi un predicato che restituisca `true` solo quando la stringa è sia non-null che non-blank, con `notNull` controllato *per primo* in modo che `notBlank` non venga mai eseguito su un `null`. Quale espressione fa questo correttamente?
Hai `Predicate<String> notNull = Objects::nonNull;` e `Predicate<String> notBlank = s -> !s.isBlank();` e vuoi un predicato che restituisca `true` solo quando la stringa è sia non-null che non-blank, con `notNull` controllato *per primo* in modo che `notBlank` non venga mai eseguito su un `null`. Quale espressione fa questo correttamente?
Was this page helpful?