Avatar ·

Как отфильтровать коллекцию в Java по условию (предикату)?

Всем привет! Я новичок в Java и столкнулся с такой проблемой: у меня есть список объектов `Person` с полями `name` и `age`. Мне нужно получить только тех, кто старше 18 лет. Я знаю, что можно пройтись циклом `for` и добавлять подходящие элементы в новый список, но это выглядит громоздко. Я слышал, что в Java 8 появились стримы и лямбды, но пока не разобрался, как их применить для фильтрации коллекций. Вот пример моего кода: ```java List people = new ArrayList<>(); people.add(new Person("Alice", 22)); people.add(new Person("Bob", 17)); people.add(new Person("Charlie", 19)); // Как получить список совершеннолетних? // Пока делаю так: List adults = new ArrayList<>(); for (Person p : people) { if (p.getAge() >= 18) { adults.add(p); } } ``` Это работает, но код получается длинным, особенно если нужно фильтровать по разным условиям. Есть ли более элегантный способ? Может, какой-то метод вроде `filter()` или библиотека Apache Commons? Или как правильно использовать `Predicate` в Java? Буду благодарен за примеры с пояснениями. <<>>

Использование Stream API и Predicate для фильтрации коллекций

Ваш подход с циклом for абсолютно рабочий, но с Java 8 появился более лаконичный и функциональный способ — Stream API. Он позволяет обрабатывать коллекции в стиле "цепочки вызовов" (pipeline). Для фильтрации используется метод filter(), который принимает объект Predicate — функциональный интерфейс с методом test(T t), возвращающим boolean.

Пример 1: Фильтрация через лямбда-выражение

Самый простой способ — передать лямбду прямо в filter():

List<Person> adults = people.stream()
    .filter(p -> p.getAge() >= 18)
    .collect(Collectors.toList());

Здесь .stream() создаёт поток из списка, filter оставляет только элементы, удовлетворяющие условию, а collect(Collectors.toList()) собирает результат обратно в список.

Пример 2: Вынос условия в отдельный предикат

Если условие сложное или используется многократно, его можно вынести в переменную типа Predicate:

Predicate<Person> isAdult = person -> person.getAge() >= 18;
List<Person> adults = people.stream()
    .filter(isAdult)
    .collect(Collectors.toList());

Пример 3: Комбинирование нескольких предикатов

Предикаты можно объединять через методы and(), or(), negate():

Predicate<Person> isAdult = p -> p.getAge() >= 18;
Predicate<Person> nameStartsWithA = p -> p.getName().startsWith("A");
List<Person> result = people.stream()
    .filter(isAdult.and(nameStartsWithA)) // взрослые И с именем на A
    .collect(Collectors.toList());

Пример 4: Фильтрация с помощью метода ссылки

Если у вас уже есть метод, проверяющий условие, можно использовать ссылку на метод:

public static boolean isAdult(Person p) {
    return p.getAge() >= 18;
}

// Использование:
List<Person> adults = people.stream()
    .filter(MyClass::isAdult)
    .collect(Collectors.toList());

Альтернативы без Stream API

  • Java 8+ с методом removeIf() — изменяет исходную коллекцию, удаляя элементы, не прошедшие условие (обратная логика):
    people.removeIf(p -> p.getAge() < 18);
  • Apache Commons Collections — утилита CollectionUtils.filter(), но она тоже мутирует коллекцию и требует зависимость.
  • Google Guava — класс FluentIterable с методом filter(), похож на Stream API.

Рекомендация

Для современных проектов на Java 8+ используйте Stream API — это стандартный, читаемый и гибкий инструмент. Он не меняет исходную коллекцию (если не использовать removeIf), что безопаснее. Лямбды и ссылки на методы делают код компактным, а комбинирование предикатов позволяет строить сложные условия без вложенных циклов.

Ваш пример с циклом можно переписать в одну строку — и это будет понятно любому Java-разработчику, знакомому с функциональным стилем.

<<>>

Использование метода removeIf() для фильтрации с изменением исходной коллекции

Если вам не нужно сохранять исходный список, а требуется отфильтровать его "на месте", можно воспользоваться методом removeIf(), который появился в Java 8 в интерфейсе Collection. Он принимает Predicate и удаляет все элементы, удовлетворяющие условию.

Важно: логика обратная — вы передаёте предикат для удаления, а не для сохранения. Чтобы оставить только взрослых, нужно удалить тех, кто младше 18:

people.removeIf(p -> p.getAge() < 18);
// Теперь people содержит только совершеннолетних

Когда это удобно?

  • Когда исходная коллекция больше не нужна в неизменном виде.
  • Когда вы работаете с большими данными и не хотите создавать новый список (экономия памяти).
  • В случаях, когда нужно модифицировать коллекцию, переданную в метод, и вернуть её же (например, в builder-паттернах).

Недостатки

  • Метод изменяет исходный объект, что может привести к побочным эффектам.
  • Нельзя использовать с неизменяемыми коллекциями (например, List.of() выбросит исключение).
  • Не подходит, если нужно сохранить обе версии (исходную и отфильтрованную).

В остальном это простой и эффективный способ, особенно когда логика фильтрации тривиальна и вы уверены, что исходный список не понадобится.