Predicate в Java: функциональный интерфейс и примеры
Этот код выглядит логично, но печатает true:
Predicate<String> notEmpty = String::isEmpty;
notEmpty.negate();
System.out.println(notEmpty.test("")); // true, хотя ожидали false Предикат в Java неизменяем: negate() ничего не меняет внутри объекта, а возвращает новый предикат. Строку нужно писать так: Predicate<String> notEmpty = empty.negate();. Разберёмся, как устроен Predicate и что ещё он умеет.
Что такое Predicate в Java
Predicate — это встроенный функциональный интерфейс из пакета java.util.function, добавленный в Java SE 8. Он принимает один аргумент типа T и возвращает boolean — результат проверки условия.

Само слово «предикат» пришло из логики: предикат — это утверждение об объекте, которое либо истинно, либо ложно. «Число отрицательное», «строка пустая», «пользователь совершеннолетний» — всё это предикаты. В Java такое утверждение оформляется лямбда-выражением или ссылкой на метод.
Описание интерфейса Predicate:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
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 targetRef) { ... }
static <T> Predicate<T> not(Predicate<? super T> target) { ... } // Java 11
} Абстрактный метод здесь ровно один — test(), поэтому интерфейс помечен аннотацией @FunctionalInterface и его можно реализовать лямбда-выражением. Подробнее о самом понятии читайте в уроке про функциональные интерфейсы.
Функциональный дескриптор интерфейса:
T -> boolean Метод test(): первый пример
Метод test() выполняет проверку и возвращает её результат. Пример предиката, который определяет отрицательные числа:
import java.util.function.Predicate;
public class PredicateExample1 {
public static void main(String[] args) {
Predicate<Integer> negative = i -> i < 0;
System.out.println(negative.test(-6)); // true
System.out.println(negative.test(6)); // false
System.out.println(negative.test(0)); // false
}
} Обратите внимание: 0 не отрицательное число, поэтому третья проверка возвращает false. Предикат — это всегда строгий ответ «да» или «нет», третьего варианта нет.
Методы and(), or() и negate()
Кроме test(), интерфейс содержит три метода по умолчанию, которые позволяют комбинировать предикаты, не переписывая условия вручную.
| Метод | Сигнатура | Что делает | Аналог в коде |
|---|---|---|---|
| test | boolean test(T t) | Единственный абстрактный метод: выполняет проверку | условие в if |
| and | default Predicate<T> and(Predicate<? super T> other) | Логическое И двух предикатов, с коротким замыканием | a && b |
| or | default Predicate<T> or(Predicate<? super T> other) | Логическое ИЛИ двух предикатов, с коротким замыканием | a || b |
| negate | default Predicate<T> negate() | Отрицание текущего предиката | !a |
| isEqual | static <T> Predicate<T> isEqual(Object targetRef) | Предикат сравнения через Objects.equals() | Objects.equals(x, target) |
| not | static <T> Predicate<T> not(Predicate<? super T> target) | Отрицание переданного предиката (Java 11) | !a |
Все три метода and(), or() и negate() возвращают новый Predicate и не изменяют исходные объекты.
Пример с методом and():
import java.util.function.Predicate;
public class PredicateExample2 {
public static void main(String[] args) {
Predicate<String> containsA = t -> t.contains("A");
Predicate<String> containsB = t -> t.contains("B");
System.out.println(containsA.and(containsB).test("ABCD")); // true
System.out.println(containsA.and(containsB).test("ACD")); // false
}
} Пример с методами or() и negate():
import java.util.function.Predicate;
public class PredicateExample3 {
public static void main(String[] args) {
Predicate<Integer> negative = i -> i < 0;
Predicate<Integer> even = i -> i % 2 == 0;
// отрицательное ИЛИ чётное
System.out.println(negative.or(even).test(7)); // false
System.out.println(negative.or(even).test(-7)); // true
System.out.println(negative.or(even).test(8)); // true
// НЕ отрицательное
Predicate<Integer> nonNegative = negative.negate();
System.out.println(nonNegative.test(0)); // true
}
} Важно
Внутри and() и or() используются операторы && и ||, то есть работает короткое замыкание. Если a.test(x) вернул false, то в a.and(b) предикат b вызван не будет. Это важно, когда второй предикат тяжёлый (запрос в базу) или защищает от NullPointerException.
Почему в сигнатуре стоит Predicate<? super T>, а не Predicate<T>? Потому что предикат-потребитель может быть объявлен для более общего типа. Проверка «объект не равен null» описывается как Predicate<Object> и прекрасно годится для строк:
Predicate<Object> notNull = Objects::nonNull;
Predicate<String> longString = s -> s.length() > 5;
Predicate<String> safe = longString.and(notNull); // компилируется благодаря ? super T Статические методы isEqual() и not()
У интерфейса есть и статические методы-фабрики.
Predicate.isEqual(Object targetRef) (Java 8) возвращает предикат, который сравнивает аргумент с эталоном через Objects.equals() — то есть корректно работает с null:
Predicate<String> isJava = Predicate.isEqual("Java");
System.out.println(isJava.test("Java")); // true
System.out.println(isJava.test("Kotlin")); // false
System.out.println(isJava.test(null)); // false, без NullPointerException Predicate.not(Predicate<? super T> target) появился в Java 11. Он нужен там, где нельзя вызвать negate() на ссылке на метод напрямую:
import java.util.List;
import java.util.function.Predicate;
public class PredicateNotExample {
public static void main(String[] args) {
List<String> lines = List.of("Java", " ", "Predicate", "");
// String::isBlank.negate() не компилируется
List<String> result = lines.stream()
.filter(Predicate.not(String::isBlank))
.toList();
System.out.println(result); // [Java, Predicate]
}
} Predicate в Stream API и removeIf()
Главное практическое применение Predicate — фильтрация данных. Метод filter() из Stream API принимает именно предикат:
import java.util.List;
import java.util.function.Predicate;
public class PredicateStreamExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(-3, 0, 5, 12, -7, 8);
Predicate<Integer> positive = n -> n > 0;
Predicate<Integer> even = n -> n % 2 == 0;
List<Integer> positiveEven = numbers.stream()
.filter(positive.and(even))
.toList();
System.out.println(positiveEven); // [12, 8]
System.out.println(numbers.stream().anyMatch(even)); // true
System.out.println(numbers.stream().allMatch(positive)); // false
System.out.println(numbers.stream().noneMatch(n -> n > 100)); // true
}
} Предикат принимают и anyMatch(), allMatch(), noneMatch(), takeWhile(), dropWhile() — полный разбор в уроке про методы Stream API.
Вне стримов предикат используется, например, в Collection.removeIf():
import java.util.ArrayList;
import java.util.List;
public class RemoveIfExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(List.of(1, -2, 3, -4));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [1, 3]
}
} О версиях
Терминальная операция Stream.toList() доступна с Java 16, а List.of() — с Java 9. На Java 8 замените их на collect(Collectors.toList()) и Arrays.asList(...): сам интерфейс Predicate при этом работает одинаково начиная с Java 8.
BiPredicate, IntPredicate и другие специализации
Помимо базового Predicate<T>, пакет java.util.function содержит его разновидности: BiPredicate<T, U> — для проверки пары значений, а IntPredicate, LongPredicate и DoublePredicate — для примитивов, чтобы избежать автоупаковки и лишних объектов. Дескрипторы, абстрактные методы и примеры кода для каждой из них — в отдельном уроке «Специализации функциональных интерфейсов».
Другие интерфейсы того же пакета разобраны в уроках Function, Consumer, Supplier и UnaryOperator.
Где чаще всего ошибаются с Predicate
1. Считают предикат изменяемым. and(), or() и negate() возвращают новый объект. Вызов без присваивания результата не делает ничего:
Predicate<Integer> positive = n -> n > 0;
positive.negate(); // результат потерян
System.out.println(positive.test(5)); // true
Predicate<Integer> nonPositive = positive.negate(); // так правильно
System.out.println(nonPositive.test(5)); // false 2. Ждут NullPointerException в момент проверки. Реализация and() начинается с Objects.requireNonNull(other), поэтому NullPointerException вылетит уже при сборке предиката, а не при вызове test():
Predicate<String> p = s -> s.isEmpty();
Predicate<String> combined = p.and(null); // NullPointerException уже здесь 3. Путают Predicate с Function<T, Boolean>. Формально оба возвращают «истину или ложь», но Function<T, Boolean> отдаёт объект-обёртку Boolean (лишняя упаковка и риск null), не имеет методов and(), or(), negate(), и его нельзя передать в filter() или removeIf().
4. Пишут громоздкие условия вместо композиции. Вместо одного длинного лямбда-выражения удобнее собрать предикат из именованных частей — код становится самодокументируемым:
Predicate<String> notNull = Objects::nonNull;
Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> startsWithJ = s -> s.startsWith("J");
Predicate<String> valid = notNull.and(notBlank).and(startsWithJ); Совет
В цепочке notNull.and(notBlank) порядок имеет значение: благодаря короткому замыканию проверка на null должна идти первой, иначе s.isBlank() упадёт с NullPointerException.
Полная спецификация интерфейса — в официальном javadoc Oracle.
Часто задаваемые вопросы
Чем Predicate отличается от Function<T, Boolean>?
Predicate возвращает примитив boolean, а Function<T, Boolean> — объект-обёртку Boolean, из-за чего добавляется автоупаковка и возможен null. Кроме того, у Predicate есть методы and(), or(), negate(), а методы вроде Stream.filter() и Collection.removeIf() принимают именно Predicate.
Вычисляются ли оба предиката в and() и or()?
Нет. Внутри используются операторы && и ||, поэтому работает короткое замыкание: в a.and(b) предикат b не вызовется, если a вернул false; в a.or(b) — если a вернул true. Поэтому дешёвые и защитные проверки ставят первыми.
Зачем нужен Predicate.not(), если есть negate()?
Метод negate() вызывается на уже существующем предикате, а ссылку на метод нельзя использовать как получателя вызова: String::isBlank.negate() не компилируется. Статический Predicate.not(String::isBlank), добавленный в Java 11, решает эту задачу и читается короче, чем лямбда с восклицательным знаком.
Как проверить примитив без автоупаковки?
Используйте специализации IntPredicate, LongPredicate и DoublePredicate — их метод test принимает примитив напрямую. Для проверки двух значений сразу подходит BiPredicate<T, U> с методом test(T, U).
Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии