Stream.findFirst: примеры (JAVA)

Примеры применения findFirst в Stream API
Раздел: Потоки данных (Stream API) - терминальные операции
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. Поиск первого элемента после сложной цепочки операций

Пример java
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 с выбрасыванием исключения при отсутствии

Пример java
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+)

Пример java
Stream.iterate(1, n -> n + 1)
    .takeWhile(n -> n <= 100)
    .filter(n -> n % 7 == 0)
    .findFirst();
Optional[7]

4. Параллельный поиск без гарантии порядка для ускорения

Пример java
// Если порядок не важен и требуется производительность
Optional<Integer> any = largeCollection.stream()
    .parallel()
    .unordered()
    .filter(x -> expensiveCheck(x))
    .findAny();
Optional[12345]  // пример найденного значения

5. Получение индекса первого совпадения (неэффективно, демонстрация)

Пример java
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. Поиск первого значения в потоках из нескольких источников

Пример java
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

Пример java
String result = Stream.of("x","y")
    .filter(s -> s.startsWith("z"))
    .findFirst()
    .map(String::toUpperCase)
    .orElseGet(() -> "DEF");
System.out.println(result);
DEF

Примеры демонстрируют, как findFirst сочетается с другими операциями, а также показывают компромиссы между детерминированностью и производительностью в параллельных потоках.

джава Stream.findFirst function comments

En
Stream.findFirst Returns an Optional describing the first element of the stream