Безопасно ли для метода возвращать Stream <T>?

У меня есть ситуация, когда я читаю базу данных и возвращаю List<String>, где каждая строка выбирается и добавляется в список в соответствии с некоторыми критериями. Подпись метода:

public List<String> myMethod(String query, int limit)

Второй параметр предоставляет верхнюю границу размера возвращаемого списка (установка limit=-1 приведет к удалению любого ограничения по размеру). Чтобы избежать интенсивного использования этого метода, я написал эквивалентный метод, который возвращает Stream<String> вместо списка. (Примечание. Мне не нужен случайный доступ к возвращенным элементам или любой другой функции, специфичной для списка.)

Однако я немного скептически отношусь к возврату Stream<>, тем более, что метод является общедоступным. Безопасно ли иметь общедоступный метод, возвращающий Stream<> в Java?

Ответ 1

Не только безопасно, но рекомендуется главным архитектором Java.

Особенно, если ваши данные основаны на I/O и, таким образом, еще не материализованы в памяти в момент вызова myMethod, было бы очень желательно вернуть Stream вместо List. Клиенту может потребоваться только часть или его часть или некоторые данные фиксированного размера. Таким образом, у вас есть возможность перейти от O (n) к памяти O (1).

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

Ответ 2

Я считаю, что по умолчанию вы должны избегать Stream в ваших интерфейсах открытых методов, потому что они опасны для использования, см. Как безопасно использовать Java Streams безопасно без методов isFinite() и isOrdered()?

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

Поэтому я бы даже рассмотрел Stream как возвращаемое значение, если возвращаемые вами данные еще не материализованы, и вы хотите, чтобы ваши клиенты сами решали, как их реализовать. Но даже тогда, Iterable или Iterator кажутся лучшим выбором, потому что они приходят без ненужного параллельного обработанного багажа, который есть у потоков, и что защитное программирование должно остерегаться.

Например, при возврате List ваши клиенты знают, что возвращаемый тип данных является конечным и упорядоченным, и его итерирование не будет неожиданно выполняться параллельно на ForkJoinPool, возможно, нарушающем все ваше приложение. В Stream вы должны вызывать sequential() для защиты от этой возможности.

Если источник данных должен закрываться после потребления, я бы предпочел вариант InputStream Stream, потому что разработчики будут хорошо помнить, что им нужно закрыть поток (и статические контролеры напомнят им).