Потребление потока несколько раз с Stream.sum () - PullRequest
5 голосов
/ 17 апреля 2020

Поток должен быть запущен (вызывая промежуточную или терминальную операцию потока) только один раз.

Я понял, но как выяснить, что сумма потока не потребляет Это? Я могу запустить приведенный ниже код без каких-либо исключений.

double totalPrice = stream.mapToDouble(product -> product.price).sum();
List<Product> products = stream.map(this::convert).collect(Collectors.toList());

Почему sum не является оператором терминала? Чем он отличается от сбора элементов потока в список?

1 Ответ

7 голосов
/ 17 апреля 2020

Документация

Не указано, может ли поток использоваться один или несколько раз. Документация гласит: « должен использоваться ... только один раз », а не « must » или « can ».

It зависит от базовой реализации и источника потока. Таким образом, возможность выполнять его несколько раз составляет undefined для потоков в целом.


Реализации

Возможно, вы сможете найти поток, который может быть используется дважды или несколько раз, особенно для пользовательских реализаций или реализаций, где обнаружение предотвращения может быть слишком дорогим для реализации.

Но, опять же, оно не поддерживается документацией. Это может быть в текущей реализации, но это может измениться в любое время, в любом выпуске Java. Он может генерировать исключения, это может привести к ошибочному поведению, оно не указано.

Вот небольшой пример, который использует sum(), который генерирует исключение, потому что у него есть такое обнаружение (JDK 11):

Stream<Integer> stream = List.of(1, 2, 3, 4).stream();

int first = stream
    .mapToInt(i -> i)
    .sum();

int second = stream
    .mapToInt(i -> i)
    .sum();

// throws IllegalStateException: stream has already been operated upon or closed

Заключение

Определенно предполагается, что не будет использовать поток несколько раз , как указано в документации. Поэтому, даже если это возможно для определенной реализации, избегайте ее .


Почему?

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

Потоки имеют большую область действия , чем коллекции. Поток, созданный в типичной коллекции, может легко поддерживать несколько итераций. Но поток, связанный с ресурсом, таким как файл или веб-соединение, например, поток, возвращенный Files.lines(...), не может. Такая функция будет чрезвычайно ресурсоемкой и дорогой для файлов и, возможно, даже не будет поддерживаться для других, как, например, веб-соединение, которое впоследствии может быть закрыто.

Более того, вы можете легко создать бесконечный поток, который генерирует случайные числа:

Stream<Double> stream = Stream.generate(Math::random);

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

Другой пример, поток, который потребляет ресурсы без восстановление их:

Queue<Integer> queue = ...
Stream<Integer> stream = Stream.generate(queue::poll);

Этот поток удалит элементы из очереди. Они ушли после использования потока. Еще одна итерация потока больше не сможет заполучить мертвые объекты.

...