Безопасно ли, чтобы метод возвращал Stream‹T›?

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

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

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

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


person Chthonic Project    schedule 15.01.2015    source источник
comment
В некоторых случаях может быть понятнее и проще вернуть Iterable/Iterator. Я часто создаю Iterator<ResultSet> в своем коде. Сделать Stream из Iterator довольно просто.   -  person OldCurmudgeon    schedule 15.01.2015
comment
@OldCurmudgeon Если вы вернете Stream, вы получите Iterator почти бесплатно (stream.iterator()).   -  person Marko Topolnik    schedule 15.01.2015
comment
@Marko - Согласен, но вы по-прежнему обратно совместимы с Java 7 и более ранними версиями, если затем предложите простой адаптер для потоковой передачи Iterator в Java 8.   -  person OldCurmudgeon    schedule 15.01.2015
comment
@OldCurmudgeon Iterator не Closeable, и это вызывает некоторые головные боли для итераторов, поддерживаемых вводом-выводом.   -  person Marko Topolnik    schedule 15.01.2015


Ответы (2)


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

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

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

person Marko Topolnik    schedule 15.01.2015
comment
Спасибо за ссылку на ответ Брайана Гетца. У меня осталось только одно сомнение в безопасности: возврат Stream<> в общедоступном методе означает, что я не могу гарантировать, что он будет закрыт. Я заметил, что Javadoc говорит почти все экземпляры потока на самом деле не нужно закрывать после использования, но мне не удалось найти более подробную информацию об этом подозрительном слове «почти». - person Chthonic Project; 15.01.2015
comment
@ChthonicProject Ну, это очень просто: если ваш поток поддерживается ресурсом ввода-вывода, то его обязательно нужно закрыть. Это выбор, который вы должны сделать --- жадно копировать все в кучу, освобождая ресурс ввода-вывода, или иметь поток, который нужно закрыть. Но я бы сказал следующее: тривиально иметь метод, который безопасно преобразует поток, поддерживаемый вводом-выводом, в список в куче; другое направление невозможно. Таким образом, вы не добавляете ничего, кроме гибкости, используя Stream. - person Marko Topolnik; 15.01.2015
comment
Не заметил важного предложения, которое следует в том же абзаце в Javadoc. Делюсь этим здесь, чтобы поделиться радостью: Если поток требует закрытия, его можно объявить как ресурс в операторе try-with-resources. Это вместе с вашим комментарием (и, конечно же, , главный ответ), заканчиваются все мои сомнения и колебания. - person Chthonic Project; 15.01.2015
comment
Да, потоки AutoCloseable. - person Marko Topolnik; 15.01.2015

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

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

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

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

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

person tkruse    schedule 27.06.2019