«1. Обзор

В этой краткой статье мы собираемся обсудить распространенное исключение, с которым мы можем столкнуться при работе с классом Stream в Java 8:

IllegalStateException: stream has already been operated upon or closed.

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

2. Причина

В Java 8 каждый класс Stream представляет одноразовую последовательность данных и поддерживает несколько операций ввода-вывода.

A Stream should be operated on (invoking an intermediate or terminal stream operation) only once. A Stream implementation may throw IllegalStateException if it detects that the Stream is being reused.

Всякий раз, когда над объектом Stream вызывается терминальная операция, экземпляр потребляется и закрывается.

Таким образом, нам разрешено выполнять только одну операцию, которая использует поток, в противном случае мы получим исключение, указывающее, что поток уже был обработан или закрыт.

Давайте посмотрим, как это можно перевести на практический пример:

Stream<String> stringStream = Stream.of("A", "B", "C", "D");
Optional<String> result1 = stringStream.findAny(); 
System.out.println(result1.get()); 
Optional<String> result2 = stringStream.findFirst();

В результате:

A
Exception in thread "main" java.lang.IllegalStateException: 
  stream has already been operated upon or closed

После вызова метода #findAny() строковый поток закрывается, поэтому дальнейшие действия операция над потоком вызовет исключение IllegalStateException, что и произошло после вызова метода #findFirst().

3. Решение

Проще говоря, решение состоит в создании нового потока каждый раз, когда он нам нужен.

Мы можем, конечно, сделать это вручную, но здесь функциональный интерфейс Поставщика становится очень удобным:

Supplier<Stream<String>> streamSupplier 
  = () -> Stream.of("A", "B", "C", "D");
Optional<String> result1 = streamSupplier.get().findAny();
System.out.println(result1.get());
Optional<String> result2 = streamSupplier.get().findFirst();
System.out.println(result2.get());

В результате:

A
A

Мы определили объект streamSupplier с типом Stream\u003cString\u003e, точно такого же типа, который возвращает метод #get(). Поставщик основан на лямбда-выражении, которое не принимает входных данных и возвращает новый поток.

Вызов функционального метода get() для Supplier возвращает только что созданный объект Stream, над которым мы можем безопасно выполнить другую операцию Stream.

5. Заключение

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

Вы можете найти полный исходный код и все фрагменты кода для этой статьи на GitHub.