Stream.findFirst: примеры (JAVA)
Stream.findFirst: OptionalОписание
Метод findFirst() из пакета java.util.stream - терминальная операция потоков Java, возвращающая первый встреченный элемент в потоке. Для ссылочных потоков возвращается Optional<T>, для примитивных потоков - соответствующий тип-обертка: OptionalInt, OptionalLong, OptionalDouble.
Синтаксические варианты:
Optional<T> Stream<T>.findFirst()OptionalInt IntStream.findFirst()OptionalLong LongStream.findFirst()OptionalDouble DoubleStream.findFirst()
Метод не принимает аргументов. Поведение и возвращаемые значения:
- Возвращается
Optionalтипа с найденным значением, если поток содержит элемент; в противном случае возвращается пустой Optional. findFirst- короткозамыкающая (short-circuit) терминальная операция: в подходящем случае дальнейшая обработка элементов не выполняется после нахождения результата.- При упорядоченных потоках гарантируется первый по порядку элемент. Для неупорядоченных потоков порядок не определён.
- В параллельных потоках при упорядоченных источниках требуется синхронизация результата первого по порядку элемента, что может снизить производительность по сравнению с findAny.
- Если поток бесконечен, последовательный
findFirstнайдет первый элемент быстро (в зависимости от операций-предшественников); в параллельном исполнении для упорядоченных источников может потребоваться дополнительная координация.
Типичные вспомогательные операции для работы с результатом: isPresent(), ifPresent(...), orElse(...), orElseGet(...), orElseThrow(), map(...).
Особенности
- Не бросает
NullPointerExceptionсама по себе; исключения возможны при некорректной работе с содержимым Optional (например, вызовget()на пустом Optional приводит кNoSuchElementException). - Не принимает предикаты - фильтрация выполняется до вызова через
filter. - Нельзя ожидать детерминированности результата от
findFirstна неупорядоченных параллельных потоках; для производительности предпочтительнееfindAny, если порядок не важен.
Короткие примеры
Пример 1. Простой последовательный поток, первый элемент:
List<String> list = List.of("a", "b", "c");
Optional<String> opt = list.stream().findFirst();
System.out.println(opt);
Optional[a]
Пример 2. Пустой поток:
List<Integer> empty = List.of();
Optional<Integer> optEmpty = empty.stream().findFirst();
System.out.println(optEmpty.isPresent());
false
Пример 3. С фильтром, когда совпадений нет:
List<Integer> nums = List.of(1, 2, 3);
Optional<Integer> none = nums.stream()
.filter(n -> n > 10)
.findFirst();
System.out.println(none);
Optional.empty
Пример 4. Примитивный поток:
IntStream.range(0, 5).findFirst();
OptionalInt[0]
Пример 5. Различие между последовательным и параллельным для упорядоченного источника:
List<String> items = List.of("x", "y", "z");
System.out.println(items.stream().parallel().findFirst());
System.out.println(items.stream().findAny());
Optional[x] Optional[x] // findAny на последовательном потоке тоже может вернуть x
Похожие возможности в Java
findAny
Возвращает любой элемент, удовлетворяющий условиям. В неупорядоченных или параллельных потоках обычно быстрее, когда порядок не важен. Для упорядоченных последовательных потоков по смыслу может совпадать с findFirst.
limit и reduce
Можно получить первый элемент через stream().limit(1).collect(...) или через редуцирование, но это менее удобно по сравнению с Optional-инфраструктурой findFirst.
forEachOrdered
В случае необходимости гарантированного упорядоченного перебора элементов без получения объекта Optional предпочтение может быть отдано forEachOrdered, но это уже не возвращает отдельный элемент.
Сравнение
findFirst- для детерминированного первого элемента в упорядоченном потоке.findAny- для возможной оптимизации в параллели, когда порядок не важен.
Аналоги в других языках
JavaScript
В массивах: Array.prototype.find возвращает первый элемент, проходящий предикат или undefined.
const arr = [1,2,3];
console.log(arr.find(x => x % 2 === 0));
2
Python
Стандартного метода в коллекциях нет, но часто используется next с генератором, возвращается значение или None.
lst = [1,2,3]
res = next((x for x in lst if x % 2 == 0), None)
print(res)
2
C#
LINQ: FirstOrDefault() или First(). First() бросит исключение при отсутствии; FirstOrDefault() вернет значение по умолчанию.
var a = new[] {1,2,3};
Console.WriteLine(a.FirstOrDefault(x => x % 2 == 0));
2
Kotlin
Коллекции: firstOrNull { predicate }, возвращает значение или null.
val list = listOf(1,2,3)
println(list.firstOrNull { it % 2 == 0 })
2
Go
Нет встроенной функции; используется явный цикл по срезу.
arr := []int{1,2,3}
var found int
for _, v := range arr {
if v%2==0 { found = v; break }
}
fmt.Println(found)
2
PHP
Обычно используется цикл или array_filter с reset для получения первого элемента. Результаты и поведение отличаются от Optional-стиля Java - может вернуться false при неудаче.
SQL
SELECT ... LIMIT 1 - аналог поиска первого подходящего результата в наборе данных.
Отличия от Java: в большинстве языков нет отдельной Optional-обертки с набором безопасных методов; поведение при отсутствии результата варьируется (null, undefined, значение по умолчанию, исключение).
Типичные ошибки
Использование get() без проверки
Optional<String> opt = Stream.empty().findFirst();
String s = opt.get(); // без проверки
Exception in thread "main" java.util.NoSuchElementException: No value present at java.base/java.util.Optional.get(Optional.java:143) ...
Ожидание порядка в неупорядоченном параллельном потоке
Если поток получен из структуры, которая не гарантирует порядок (например, ConcurrentHashMap.values().stream().parallel().unordered()), результат findFirst может не соответствовать ожиданиям первого логического элемента.
Непонимание стоимости в параллели
Упорядоченные параллельные потоки при вызове findFirst могут требовать дополнительной синхронизации, что снижает выигрыш от параллельности.
Побочные эффекты в промежуточных операциях
Наличие побочных эффектов в map/peek и ранний выход через findFirst может привести к частично выполненным побочным действиям, что затрудняет отладку и ухудшает читаемость.
Изменения по версиям
Метод findFirst() присутствует с Java 8 как часть Stream API и в последующих версиях не претерпел значительных изменений в поведении. Небольшие изменения вокруг сопутствующих API (Optional, вспомогательных методов, улучшения производительности параллельных стримов) появлялись в новых релизах, но контракт findFirst остался прежним.
Продвинутые и редкие примеры
1. Поиск первого элемента после сложной цепочки операций
Optional<String> first = Stream.of("aa","bb","ccc","dd")
.flatMap(s -> Stream.of(s, s.toUpperCase()))
.filter(s -> s.length() > 2)
.findFirst();
System.out.println(first);
Optional[ccc]
2. Обработка Optional с выбрасыванием исключения при отсутствии
String mustExist = Stream.empty()
.findFirst()
.orElseThrow(() -> new IllegalStateException("Нет элементов"));
System.out.println(mustExist);
Exception in thread "main" java.lang.IllegalStateException: Нет элементов at ...
3. Комбинация с takeWhile / dropWhile (Java 9+)
Stream.iterate(1, n -> n + 1)
.takeWhile(n -> n <= 100)
.filter(n -> n % 7 == 0)
.findFirst();
Optional[7]
4. Параллельный поиск без гарантии порядка для ускорения
// Если порядок не важен и требуется производительность
Optional<Integer> any = largeCollection.stream()
.parallel()
.unordered()
.filter(x -> expensiveCheck(x))
.findAny();
Optional[12345] // пример найденного значения
5. Получение индекса первого совпадения (неэффективно, демонстрация)
AtomicInteger idx = new AtomicInteger(0);
OptionalInt index = IntStream.range(0, list.size())
.filter(i -> predicate(list.get(i)))
.findFirst();
System.out.println(index);
OptionalInt[4] // индекс первого совпадения
6. Поиск первого значения в потоках из нескольких источников
Stream<String> s1 = Stream.of();
Stream<String> s2 = Stream.of("a");
Stream<String> s3 = Stream.of("b");
Optional<String> first = Stream.of(s1, s2, s3)
.flatMap(Function.identity())
.findFirst();
System.out.println(first);
Optional[a]
7. Обработка результата через map и orElseGet
String result = Stream.of("x","y")
.filter(s -> s.startsWith("z"))
.findFirst()
.map(String::toUpperCase)
.orElseGet(() -> "DEF");
System.out.println(result);
DEF
Примеры демонстрируют, как findFirst сочетается с другими операциями, а также показывают компромиссы между детерминированностью и производительностью в параллельных потоках.