Stream.filter: примеры (JAVA)
Stream.filter(Predicate super T> predicate): StreamОбщее описание метода Stream.filter
Метод Stream.filter из пакета java.util.stream применяется для отсеивания элементов потока по булевому условию. Это промежуточная операция, которая получает в качестве аргумента предикат и возвращает новый Stream, содержащий только те элементы исходного потока, для которых предикат возвращает true. Операция ленивaя: выполнение предиката откладывается до вызова терминальной операции.
Сигнатуры (основные):
Stream- стандартный объектный поток.filter(Predicate super T> predicate) IntStream filter(IntPredicate predicate),LongStream filter(LongPredicate predicate),DoubleStream filter(DoublePredicate predicate)- примитивные варианты для экономии упаковки.
Аргументы:
predicate: реализация функционального интерфейсаPredicate(для объектов) или соответствующего примитивного предиката. Не допускаетсяnull; передачаnullприводит кNullPointerException.
Возвращаемое значение:
- Новый промежуточный
Streamтого же типа, что и исходный (или примитивный поток для соответствующих перегрузок). Возврат всегда ненулевой ссылки.
Особенности поведения:
- Операция не изменяет исходный источник данных, она создает цепочку промежуточных операций.
- Выполнение предиката происходит только при выполнении терминальной операции.
- Порядок прохода сохраняется для упорядоченных потоков, если не используется операция, отменяющая порядок.
- Не является по определению короткозамыкающей: сама по себе фильтрация не завершит работу раньше, чем требует терминальная операция; однако совокупность операций (например, с
findFirstилиanyMatch) может приводить к раннему завершению. - Использование изменяемого состояния внутри предиката опасно при параллельной обработке.
Короткие примеры применения
Фильтрация простого списка строк, оставление тех, что длиннее 3 символов, и вывод результата:
List list = List.of("one", "two", "three", "four");
List res = list.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
System.out.println(res);
[three, four]
Использование примитивного потока IntStream:
int[] arr = {1, 2, 3, 4, 5, 6};
int sumEven = IntStream.of(arr)
.filter(n -> n % 2 == 0)
.sum();
System.out.println(sumEven);
12
Отсечение null значений:
List maybeNull = Arrays.asList("a", null, "b");
List noNulls = maybeNull.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
System.out.println(noNulls);
[a, b]
Комбинация предикатов (and / negate):
List nums = List.of(1,2,3,4,5,6);
Predicate even = n -> n % 2 == 0;
Predicate gtThree = n -> n > 3;
List out = nums.stream()
.filter(even.and(gtThree))
.collect(Collectors.toList());
System.out.println(out);
[4, 6]
Похожие механизмы в Java
Внутри экосистемы Java существуют связанные по смыслу конструкции:
- Collection.removeIf - удаляет элементы из изменяемой коллекции по предикату; полезно при необходимости изменить саму коллекцию, тогда как
Stream.filterсоздает новый результат. - Optional.filter - фильтрация значения внутри
Optional; возвращает пустой Optional при несоответствии. - Stream.map + flatMap - при необходимости преобразования элементов перед фильтрацией иногда применяется последовательность
mapиfilterилиflatMap. - Collectors.filtering (добавлен в коллекторах) - позволяет выполнять фильтрацию внутри агрегирующих операций, например, при группировке, чтобы избежать дополнительной промежуточной фильтрации.
Выбор зависит от задачи: для немодифицируемого конвейера потоков предпочтителен Stream.filter. Для прямого удаления из коллекции - removeIf. Для опционального значения - Optional.filter. Для фильтрации внутри группирующих операций - Collectors.filtering.
Аналоги в других языках и отличия
Краткие сопоставления с примерами.
- JavaScript -
Array.prototype.filter:const arr = [1,2,3,4]; const ev = arr.filter(n => n % 2 === 0); console.log(ev);[2, 4]
Отличия: метод массивов немедленно возвращает новый массив и не ленив, в отличие от Java Stream. - Python - встроенная
filterи списковые включения:nums = [1,2,3,4] even = list(filter(lambda x: x%2==0, nums)) print(even) # или print([x for x in nums if x%2==0])[2, 4] [2, 4]
Отличия:filterвозвращает итератор в Python 3; списковые включения более питоничны. - PHP -
array_filter:$a = [1,2,3,4]; $r = array_filter($a, fn($x) => $x % 2 == 0); print_r(array_values($r));Array ( [0] => 2 [1] => 4 )Отличия: возвращает массив, ключи по умолчанию сохраняются. - SQL - оператор
WHERE:SELECT id FROM table WHERE active = 1;-- результат набора строк, отфильтрованных на уровне СУБД
Отличия: фильтрация выполняется базой данных, может быть оптимизирована планировщиком. - C# - LINQ:
Where:var nums = new[]{1,2,3,4}; var ev = nums.Where(n => n % 2 == 0).ToList(); Console.WriteLine(string.Join(", ", ev));2, 4
Отличия: LINQ использует ленивую семантику, похожую на Java Stream, но семантика исполнения и интеграция с языком отличаются. - Kotlin -
filterрасширение коллекций:val a = listOf(1,2,3,4) val ev = a.filter { it % 2 == 0 } println(ev)[2, 4]
Отличия: для коллекций Kotlin фильтрация немедленная; для последовательностей (Sequence) - ленивa, как в Java. - Go - универсального встроенного аналога нет:
a := []int{1,2,3,4} var out []int for _, v := range a { if v%2 == 0 { out = append(out, v) } } fmt.Println(out)[2 4]
Отличия: требуется явная итерация или сторонние библиотеки; начиная с generics реализация компактнее, но по-прежнему императивна. - Lua - цикл через таблицу:
local a = {1,2,3,4} local out = {} for _,v in ipairs(a) do if v % 2 == 0 then table.insert(out, v) end end for _,v in ipairs(out) do io.write(v, " ") end2 4
Главные различия: Java Stream ленивый, поддерживает параллелизм, примитивные специализированные потоки для производительности; многие языки дают немедленный результат или требуют явной итерации.
Типичные ошибки и примеры
NullPredicate и NullPointerException:
List l = List.of("a","b");
Predicate p = null;
l.stream().filter(p).count();
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:234)
at java.base/java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:...)
Повторное использование потока (IllegalStateException):
Stream s = Stream.of("a","b");
Stream f = s.filter(x -> true);
f.count();
f.forEach(System.out::println);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
Побочные эффекты в предикате при параллельной обработке (негарантированное поведение):
List src = IntStream.range(0,100).boxed().collect(Collectors.toList());
List collected = Collections.synchronizedList(new ArrayList<>());
src.parallelStream()
.filter(n -> { collected.add(n); return n % 2 == 0; })
.count();
// collected порядок и содержимое могут быть неожиданны
-- результат зависит от выполнения, возможны дубликаты и непредсказуемый порядок
Проверка условия, подразумевающего выброс checked-исключения. Predicates не поддерживают checked исключения напрямую:
List files = ...;
files.stream().filter(p -> Files.isRegularFile(p))... // OK
// но если требуется вызвать метод, который бросает IOException, нужно обернуть
// Реализация должна перехватывать или оборачивать checked исключения в RuntimeException
Изменения и эволюция
Метод Stream.filter введен в Java 8 и с тех пор сохраняет прежний API. В более поздних версиях платформы происходили дополнения, которые расширяют возможности работы с потоками, но не меняют сигнатуру filter:
- Java 9: добавлены новые операции потоков (
takeWhile,dropWhile) и коллектораCollectors.filtering, что в сочетании сfilterпозволяет выразительнее строить конвейеры. - Улучшения производительности и оптимизаций конвейера в JVM в последующих релизах, которые могут влиять на скорость выполнения цепочек со
filter, особенно в параллельных потоках.
Сигнатуры примитивных перегрузок (IntStream.filter и т. п.) также остаются без изменений.
Расширенные и нестандартные примеры
Фильтрация по типу с приведением (используется Class::isInstance и map):
List
[two, four]
Использование Collectors.filtering вместе с groupingBy:
List names = List.of("Ann", "Bob", "Alice", "Bill");
Map> m = names.stream()
.collect(Collectors.groupingBy(s -> s.charAt(0),
Collectors.filtering(s -> s.length() > 3, Collectors.toList())));
System.out.println(m);
{A=[Alice], B=[Bill]}
Сочетание с flatMap для фильтрации вложенных коллекций:
List> matrix = List.of(List.of(1,2), List.of(3,4));
List ev = matrix.stream()
.flatMap(Collection::stream)
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(ev);
[2, 4]
Фильтрация с безопасной обработкой checked-исключений (обертка в функцию):
static Predicate safeIsRegularFile() {
return p -> {
try {
return Files.isRegularFile(p);
} catch (Exception e) {
throw new UncheckedIOException(new IOException(e));
}
};
}
// использование
// paths.stream().filter(safeIsRegularFile()).forEach(...);
-- эквивалентно: выброс Runtime-обертки вместо checked исключения
Параллельный поток и сохранение порядка при фильтрации (пример контролируемой параллельности):
List nums = IntStream.range(0,1000).boxed().collect(Collectors.toList());
List res = nums.parallelStream()
.filter(n -> n % 3 == 0)
.sorted() // сортировка восстанавливает порядок
.collect(Collectors.toList());
System.out.println(res.subList(0,5));
[0, 3, 6, 9, 12]
Фильтрация с запоминанием предыдущего элемента в потоке (stateful filter - редко рекомендуется):
AtomicInteger last = new AtomicInteger(Integer.MIN_VALUE);
List seq = List.of(1,1,2,2,3,1,1);
List compact = seq.stream()
.filter(i -> {
int prev = last.getAndSet(i);
return prev != i;
})
.collect(Collectors.toList());
System.out.println(compact);
[1, 2, 3, 1]
Комментарий: stateful-предикаты ломают параллельность и делают код менее прозрачным; применять осмотрительно.