Lambda ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-09-06

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: A Comprehensive Guide with Examples photo

Само слово «предикат» пришло из логики: предикат — это утверждение об объекте, которое либо истинно, либо ложно. «Число отрицательное», «строка пустая», «пользователь совершеннолетний» — всё это предикаты. В 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).

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.