Должен ли я вернуть коллекцию или поток?

Предположим, у меня есть метод, который возвращает доступное только для чтения представление в список участников:

class Team {
    private List<Player> players = new ArrayList<>();

    // ...

    public List<Player> getPlayers() {
        return Collections.unmodifiableList(players);
    }
}

Далее предположим, что все, что делает клиент, — это выполняет итерацию по списку один раз, немедленно. Может быть, чтобы поместить игроков в JList или что-то в этом роде. Клиент не хранит ссылку на список для последующего просмотра!

Учитывая этот распространенный сценарий, должен ли я вместо этого возвращать поток?

public Stream<Player> getPlayers() {
    return players.stream();
}

Или возвращает поток, не идиоматический в Java? Были ли потоки предназначены для того, чтобы всегда завершаться внутри того же выражения, в котором они были созданы?


person fredoverflow    schedule 10.07.2014    source источник
comment
В этой идиоме определенно нет ничего плохого. В конце концов, players.stream() — это именно такой метод, который возвращает поток вызывающей стороне. Реальный вопрос заключается в том, действительно ли вы хотите ограничить вызывающую сторону одним обходом, а также запретить ему доступ к вашей коллекции через Collection API? Может быть, вызывающий абонент просто хочет addAll переместить его в другую коллекцию?   -  person Marko Topolnik    schedule 10.07.2014
comment
Все это зависит. Вы всегда можете использовать как collection.stream(), так и Stream.collect(). Так что это зависит от вас и вызывающего абонента, который использует эту функцию.   -  person Raja Anbazhagan    schedule 04.07.2017


Ответы (9)


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

Во-первых, обратите внимание, что вы всегда можете получить Collection из Stream и наоборот:

// If API returns Collection, convert with stream()
getFoo().stream()...

// If API returns Stream, use collect()
Collection<T> c = getFooStream().collect(toList());

Итак, вопрос в том, что более полезно для ваших абонентов.

Если ваш результат может быть бесконечным, есть только один выбор: Stream.

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

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

Даже если вы знаете, что пользователь будет повторять его несколько раз или каким-либо иным образом сохранит его, вы все равно можете вместо этого вернуть Stream по той простой причине, что любое Collection, которое вы решите вставить (например, ArrayList), может не быть форма, которую они хотят, и тогда вызывающая сторона все равно должна скопировать ее. Если вы возвращаете Stream, они могут сделать collect(toCollection(factory)) и получить именно то, что им нужно.

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

Единственный случай, когда вы должны вернуть Collection, — это когда существуют строгие требования согласованности, и вы должны создать непротиворечивый снимок движущейся цели. Затем вам нужно будет поместить элементы в коллекцию, которая не изменится.

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

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

person Brian Goetz    schedule 10.07.2014
comment
Итак, нет ничего плохого само по себе в возврате Stream? Кстати, мне нравятся ваши работы, связанные с лямбдами, особенно разговоры об их реализации под капотом :) - person fredoverflow; 10.07.2014
comment
Как я уже сказал, есть несколько случаев, когда он не будет летать, например, когда вы хотите вернуть моментальный снимок во времени движущейся цели, особенно если у вас есть строгие требования к согласованности. Но в большинстве случаев Stream кажется более общим выбором, если вы не знаете что-то конкретное о том, как он будет использоваться. - person Brian Goetz; 10.07.2014
comment
Если, как говорится в вопросе, цель состоит в том, чтобы вернуть доступное только для чтения представление коллекции, уже присутствующей в куче, то ни одно из преимуществ Stream не применяется, и если вызывающему абоненту нужны услуги, предоставляемые Stream, это всего лишь один вызов. . Итак, для этого конкретного вопроса для геттера типа Stream не так уж много. - person Marko Topolnik; 10.07.2014
comment
@Marko Даже если вы так узко ограничиваете свой вопрос, я все равно не согласен с вашим выводом. Возможно, вы предполагаете, что создание Stream обходится гораздо дороже, чем обертывание коллекции неизменяемой оболочкой? (И даже если вы этого не сделаете, представление потока, которое вы получите в обертке, будет хуже, чем то, что вы получите из оригинала; поскольку UnmodifiedList не переопределяет spliterator(), вы фактически потеряете весь параллелизм.) Итог: будьте осторожны. предвзятость знакомства; вы знакомы с Collection много лет, и это может заставить вас не доверять новичку. - person Brian Goetz; 10.07.2014
comment
Вы поднимаете сильный вопрос с потерей исходного разделителя: я бы интуитивно предположил, что неизменяемая оболочка раскрывает разделитель базовой коллекции, но я уверен, что есть тонкий улов, который исключает это. Что касается дороговизны, я предполагаю, что переход от коллекции к потоку дешев, а обратное — нет (это O (n) как во времени, так и в пространстве). В-третьих, вы, очевидно, даете здесь очень познавательный и широко полезный ответ, но я все же думаю, что какое-то место должно быть посвящено рассмотрению специфики ОП. - person Marko Topolnik; 10.07.2014
comment
@MarkoTopolnik Конечно. Моя цель состояла в том, чтобы ответить на общий вопрос дизайна API, который становится часто задаваемым вопросом. Что касается стоимости, обратите внимание, что если у вас еще нет материализованной коллекции, которую вы можете вернуть или обернуть (OP делает, но часто ее нет), материализация коллекции в методе получения не является чем-то дешевле, чем возвращать поток и позволять вызывающей стороне материализовать его (и, конечно, ранняя материализация может быть намного дороже, если она не нужна вызывающей стороне или если вы возвращаете ArrayList, но вызывающая сторона хочет TreeSet.) Но Stream является новым, и люди часто предположим, что это больше $$$, чем есть. - person Brian Goetz; 10.07.2014
comment
Это интересно: может быть, это еще один случай моей предвзятости, потому что я годами жил в мире лени (Clojure) и часто прилагал большие усилия, чтобы перенести его на Java. Я не только осознаю преимущества лени, я принимаю их как должное и отказываюсь жить без них; но я также без труда различаю случаи, когда на лень ничего не купишь. Так что, может быть, я недооцениваю острую необходимость проповедовать лень людям Java, прежде чем перейти к деталям, когда это не так полезно. - person Marko Topolnik; 10.07.2014
comment
Что касается нематериализованных потоков, несколько месяцев назад я жаловался на lambda-dev на слабую поддержку параллелизма в текущем API, и у вас сложилось впечатление, что Streams API сосредоточен на обработке в памяти. , структуры с произвольным доступом --- такие, которые поддаются легкому расщеплению сверху вниз. Но, очевидно, есть много других аспектов в подходе вашей команды к этому вопросу. - person Marko Topolnik; 10.07.2014
comment
@MarkoTopolnik Хотя использование в памяти является очень важным вариантом использования, есть и другие случаи, которые имеют хорошую поддержку параллелизма, например неупорядоченные сгенерированные потоки (например, Stream.generate). Однако, где потоки плохо подходят, это реактивный вариант использования, когда данные поступают со случайной задержкой. Для этого я бы предложил RxJava. - person Brian Goetz; 10.07.2014
comment
Возможно, мы не согласны здесь, но это ограничение является важным моментом для рассмотрения. По моему опыту работы с программным обеспечением для бизнеса, основным примером лениво материализованных потоков являются потоки, поддерживаемые вводом-выводом, что означает, что они попадают в категорию случайных задержек. Потоки, поддерживаемые вводом-выводом, также обычно являются наиболее важной целью для распараллеливания, поскольку они часто огромны и требуют тяжелой обработки. Все это означает, что наиболее критической областью применения параллельных ленивых потоков является именно та область, которая не пользуется поддержкой Streams API. - person Marko Topolnik; 10.07.2014
comment
@MarkoTopolnik Я не думаю, что мы не согласны, за исключением, возможно, того, что вам могло бы понравиться, чтобы мы немного по-другому сосредоточили наши усилия. (Мы к этому привыкли; невозможно сделать всех людей счастливыми.) Центр разработки Streams сосредоточился на структурах данных в памяти; центр дизайна для RxJava фокусируется на внешних событиях. Обе хорошие библиотеки; также оба не очень хорошо себя чувствуют, когда вы пытаетесь применить их к случаям, находящимся далеко за пределами их центра разработки. Но то, что молоток — ужасный инструмент для вышивания, не означает, что с молотком что-то не так. - person Brian Goetz; 11.07.2014
comment
На самом деле это не обязательно касается моих желаний; речь идет о несоответствии между поощрением лени и распараллеливания с одной стороны и отказом от поддержки их наиболее важного варианта использования с другой. Однако я должен добавить, что у меня уже есть положительный опыт распараллеливания потоков с поддержкой ввода-вывода с использованием Streams API. У меня не было проблем с насыщением всех ядер процессора. Основные проблемы, с которыми я столкнулся, связаны с необычным шаблоном обработки, который происходит в самом конце конвейера обработки запросов. Это вызвало проблемы с транзакциями и обработкой ошибок, но Streams API не виноват. - person Marko Topolnik; 11.07.2014
comment
Поэтому я задаюсь вопросом: мне просто повезло, и в моем коде скрываются серьезные проблемы, или эта цель не так уж и далека от Streams API? Все, что мне нужно было добавить, — это фиксированную, но настраиваемую политику разбиения по размеру пакета вместо используемой по умолчанию ненастраиваемой политики арифметической прогрессии. - person Marko Topolnik; 11.07.2014
comment
@MarkoTopolnik Я думаю, вам, вероятно, немного повезло, и вы нашли что-то, что было достаточно близко к работе. Определенно есть вещи, которые мы могли бы сделать, чтобы улучшить его, но в конечном итоге вы сталкиваетесь с несоответствиями, которые слишком болезненны, чтобы пытаться их решить. Rx отличный; используйте его для того, в чем он хорош. Потоки великолепны; используйте его для того, в чем он хорош. - person Brian Goetz; 11.07.2014
comment
Что меня беспокоит в подходе Stream‹›, так это то, что в общем случае неизвестно, следует ли оборачивать вызов метода получения потока в try-with-resources или нет. - person Askar Kalykov; 27.01.2015
comment
@AskarKalykov Это верно только в том случае, если метод stream() не имеет надлежащей спецификации! К счастью, у нас есть инструменты для методов, чтобы объявлять подобные вещи, просто а) люди не пишут документы и б) люди их не читают. Но это не проблема с подходом... - person Brian Goetz; 27.01.2015
comment
@BrianGoetz Итак, в ситуации, когда вы объединяете разные потоки (происхождение которых неизвестно во время разработки) в результирующий, вы всегда должны распространять (транзитивно) подход попытки с ресурсами? И если источники известны во время разработки (все они фактически не закрываются), но ситуация может измениться через месяц или два, когда вы добавите закрываемый поток, тогда вам следует реорганизовать (также транзитивно) все использования, чтобы попытаться- с ресурсами? - person Askar Kalykov; 27.01.2015
comment
@BrianGoetz: Должен ли приведенный выше код getFooStream().collect(toList()) читать getFooStream().collect(Collectors.toList())? - person kevinarpe; 29.05.2015
comment
@kevinarpe Можно, но я обычно статически импортирую Collectors.*. - person Brian Goetz; 29.05.2015
comment
Согласитесь со всеми приведенными причинами, почему «потоки, вероятно, лучше». Тем не менее, есть одна важная причина, по которой я до сих пор предпочитаю коллекции большую часть времени. Потоки сильно усложняют отладку моего кода. Попробуй пошагово отладчиком... не получится. Так что для меня это само по себе обычно является более чем достаточной причиной для использования коллекций по умолчанию. И затем... намеренно использовать потоки только в том случае, если у меня есть достаточно веская и веская причина для этого, в этом конкретном случае. - person Kris; 17.04.2018
comment
Я большой поклонник потокового интерфейса и использую его, когда это возможно. Однако причина, по которой я предпочитаю неизменяемые коллекции в качестве возвращаемых значений, заключается в том, что Stream<T> не реализует Iterable<T>, поэтому использовать его в цикле foreach неудобно, так как вам нужно: (Iterable<T>)stream::iterator. Так как легче преобразовать коллекцию в поток, когда это необходимо, я обычно заканчиваю тем, что возвращаю неизменяемые представления. - person farsil; 17.12.2018
comment
toList() не распознается, и если я выполню Collectors.toList(), я получу ошибку типа, поскольку кажется, что toList() работает только для List‹String› (?) Каков общий способ достижения toList()? - person peschü; 08.11.2019
comment
Поскольку потоки допускают только одну терминальную операцию, не опасно ли возвращать их? Вызывающий может использовать возвращенный поток для создания двух новых, например. звоню theStream.map(...) дважды. Это не терпит неудачу до времени выполнения. В зависимости от покрытия тестами и того, как передаются ссылки на потоки, это может быть неприятно. - person Ant Kutschera; 14.10.2020
comment
@AntKutschera На самом деле нет. Методы, возвращающие Stream, всегда возвращают свежий нераспределенный поток; в этом легко убедиться, так как нет особого смысла в совместном использовании потоков. Тот, кто получает поток (независимо от того, создал ли он его сам или получил другим способом), обязан использовать его один раз. В общем, это также не очень сложно обеспечить, если вы понимаете самые основные факты о том, как работают потоки. - person Brian Goetz; 14.10.2020
comment
когда есть строгие требования к согласованности, и вам нужно создать согласованный снимок движущейся цели, может ли кто-нибудь привести пример? - person Krzysiek; 21.11.2020

У меня есть несколько замечаний к отличному ответу Брайана Гетца.

Довольно часто возвращается Stream из вызова метода в стиле «геттер». См. страницу использования потоков в javadoc Java 8 и найдите «методы... которые возвращают Stream» для пакетов, отличных от java.util.Stream. Эти методы обычно находятся в классах, которые представляют или могут содержать несколько значений или агрегатов чего-либо. В таких случаях API обычно возвращают их коллекции или массивы. По всем причинам, которые Брайан отметил в своем ответе, здесь очень удобно добавлять методы, возвращающие поток. Многие из этих классов уже имеют методы, возвращающие коллекции или массивы, потому что классы предшествуют Streams API. Если вы разрабатываете новый API и имеет смысл предоставить методы, возвращающие поток, возможно, нет необходимости также добавлять методы, возвращающие коллекцию.

Брайан упомянул стоимость «материализации» значений в коллекцию. Чтобы усилить этот момент, здесь на самом деле есть две затраты: стоимость хранения значений в коллекции (выделение памяти и копирование), а также стоимость создания значений в первую очередь. Последнюю стоимость часто можно уменьшить или избежать, воспользовавшись ленивым поведением Stream. Хорошим примером этого являются API в java.nio.file.Files:

static Stream<String>  lines(path)
static List<String>    readAllLines(path)

Мало того, что readAllLines должен хранить все содержимое файла в памяти, чтобы сохранить его в списке результатов, он также должен прочитать файл до самого конца, прежде чем он вернет список. Метод lines может вернуться почти сразу же после того, как он выполнил некоторую настройку, оставив чтение файла и перенос строки на более позднее время, когда это необходимо, или вообще не делать этого. Это огромное преимущество, если, например, звонящего интересуют только первые десять строк:

try (Stream<String> lines = Files.lines(path)) {
    List<String> firstTen = lines.limit(10).collect(toList());
}

Конечно, можно сэкономить значительный объем памяти, если вызывающая сторона фильтрует поток, чтобы возвращать только строки, соответствующие шаблону и т. д.

Идиома, которая, кажется, появляется, состоит в том, чтобы называть методы, возвращающие поток, после множественного числа имени вещей, которые они представляют или содержат, без префикса get. Кроме того, хотя stream() — это разумное имя для метода, возвращающего поток, когда есть только один возможный набор значений, которые должны быть возвращены, иногда существуют классы, которые имеют агрегацию нескольких типов значений. Например, предположим, что у вас есть некоторый объект, который содержит как атрибуты, так и элементы. Вы можете предоставить два API, возвращающих поток:

Stream<Attribute>  attributes();
Stream<Element>    elements();
person Stuart Marks    schedule 10.07.2014
comment
Отличные очки. Можете ли вы рассказать больше о том, где вы видите возникновение этой идиомы именования, и насколько она набирает обороты? Мне нравится идея соглашения об именах, делающего очевидным, что вы получаете поток, а не коллекцию, хотя я также часто ожидаю, что завершение IDE на get скажет мне, что я могу получить. - person Joshua Goldberg; 01.02.2016
comment
Меня тоже очень интересует эта идиома имен - person elect; 13.07.2016
comment
@JoshuaGoldberg JDK, кажется, принял эту идиому именования, хотя и не исключительно. Учтите: CharSequence.chars() и .codePoints(), BufferedReader.lines() и Files.lines() существовали в Java 8. В Java 9 были добавлены следующие элементы: Process.children(), NetworkInterface.addresses( ), Scanner.tokens(), Matcher.results(), java.xml.catalog.Catalog.catalogs(). Были добавлены другие методы возврата потока, которые не используют эту идиому — на ум приходит Scanner.findAll() — но идиома существительного во множественном числе, кажется, стала использоваться в JDK. - person Stuart Marks; 14.07.2016

Были ли потоки предназначены для того, чтобы всегда «завершаться» внутри того же выражения, в котором они были созданы?

Именно так они используются в большинстве примеров.

Примечание: возврат Stream ничем не отличается от возврата Iterator (допустим, с гораздо большей выразительной силой)

ИМХО лучшее решение - инкапсулировать зачем вы это делаете, а не возвращать коллекцию.

e.g.

public int playerCount();
public Player player(int n);

или если вы собираетесь считать их

public int countPlayersWho(Predicate<? super Player> test);
person Peter Lawrey    schedule 10.07.2014
comment
Проблема с этим ответом заключается в том, что автору потребуется предвидеть каждое действие, которое хочет выполнить клиент, и это значительно увеличит количество методов в классе. - person dkatzel; 10.07.2014
comment
@dkatzel Это зависит от того, являются ли конечные пользователи автором или кем-то, с кем они работают. Если конечные пользователи неизвестны, вам нужно более общее решение. Возможно, вы по-прежнему захотите ограничить доступ к базовой коллекции. - person Peter Lawrey; 10.07.2014

В то время как некоторые из наиболее известных респондентов дали отличные общие советы, я удивлен, что никто не заявил:

Если у вас уже есть "материализованный" Collection (т.е. он уже был создан до вызова - как в данном примере, где это поле-член), нет смысла преобразовывать его в Stream. Вызывающий может легко сделать это самостоятельно. Принимая во внимание, что если вызывающая сторона хочет использовать данные в их исходной форме, преобразование их в Stream вынуждает их выполнять избыточную работу по повторной материализации копии исходной структуры.

person Daniel Avery    schedule 17.05.2020

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

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

Эти различия существуют и в коллекциях, но там они являются частью очевидного контракта:

  • Все коллекции имеют размер, Iterator/Iterable может быть бесконечным.
  • Коллекции явно упорядочены или неупорядочены
  • Параллельность, к счастью, не является чем-то, что волнует коллекцию, кроме безопасности потоков.
  • Коллекции также обычно не закрываются, поэтому также не нужно беспокоиться об использовании try-with-resources в качестве защиты.

Как потребитель потока (либо из возврата метода, либо как параметра метода) это опасная и запутанная ситуация. Чтобы убедиться, что их алгоритм работает правильно, потребители потоков должны убедиться, что алгоритм не делает неправильных предположений о характеристиках потока. И это очень трудно сделать. В модульном тестировании это означало бы, что вам нужно умножить все ваши тесты, чтобы повторить их с тем же содержимым потока, но с потоками, которые

  • (конечный, упорядоченный, последовательный, требующий закрытия)
  • (конечный, упорядоченный, параллельный, требующий закрытия)
  • (конечный, неупорядоченный, последовательный, требующий закрытия)...

Защита метода записи для потоки, которые вызывают исключение IllegalArgumentException, если входной поток имеет характеристики, нарушающие ваш алгоритм, сложно, поскольку свойства скрыты.

Документация смягчает проблему, но она ошибочна и часто упускается из виду. В качестве примера см. эти javadocs файлов Java8:

 /**
  * [...] The returned stream encapsulates a Reader. If timely disposal of
  * file system resources is required, the try-with-resources 
  * construct should be used to ensure that the stream's close 
  * method is invoked after the stream operations are completed.
  */
 public static Stream<String> lines(Path path, Charset cs)
 /**
  * [...] no mention of closing even if this wraps the previous method
  */
public static Stream<String> lines(Path path)

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

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

person tkruse    schedule 22.04.2018
comment
Ваши опасения по поводу бесконечных потоков необоснованны; вопрос в том, должен ли я вернуть коллекцию или поток. Если Сбор возможен, результат по определению конечен. Так что опасения, что вызывающая сторона рискует бесконечной итерацией, учитывая, что вы могли бы вернуть коллекцию, необоснованны. Остальные советы в этом ответе просто плохие. Мне кажется, что вы столкнулись с кем-то, кто чрезмерно использовал Stream, и вы чрезмерно вращаетесь в другом направлении. Понятный, но плохой совет. - person Brian Goetz; 27.04.2019

Если поток конечен и есть ожидаемая/нормальная операция над возвращаемыми объектами, которая вызовет проверенное исключение, я всегда возвращаю коллекцию. Потому что, если вы собираетесь делать что-то с каждым из объектов, которые могут вызвать исключение проверки, вы будете ненавидеть поток. Один реальный недостаток потоков — неспособность элегантно обрабатывать проверенные исключения.

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

person designbygravity    schedule 17.04.2018

Я думаю, это зависит от вашего сценария. Может быть, если вы сделаете свой Team реализующим Iterable<Player>, этого будет достаточно.

for (Player player : team) {
    System.out.println(player);
}

или в функциональном стиле:

team.forEach(System.out::println);

Но если вам нужен более полный и плавный API, поток может быть хорошим решением.

person gontard    schedule 10.07.2014
comment
Обратите внимание, что в коде, опубликованном OP, количество игроков почти бесполезно, кроме как в качестве оценки («Сейчас играет 1034 игрока, нажмите здесь, чтобы начать!»). Это потому, что вы возвращаете неизменное представление изменяемой коллекции , поэтому количество, которое вы получите сейчас, может не совпадать с количеством через три микросекунды. Таким образом, хотя возврат коллекции дает вам простой способ получить подсчет (и действительно, stream.count() тоже довольно просто), это число на самом деле не очень важно для чего-либо, кроме отладки или оценки. - person Brian Goetz; 10.07.2014

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

Это также побуждает пользователей вашего предметного класса писать код в более современном стиле Java 8. Можно выполнить поэтапный рефакторинг в соответствии с этим стилем, сохранив существующие геттеры и добавив новые геттеры, возвращающие Stream. Со временем вы можете переписать свой устаревший код, пока окончательно не удалите все геттеры, возвращающие список или набор. Этот вид рефакторинга действительно хорош после того, как вы очистите весь устаревший код!

person Vazgen Torosyan    schedule 15.02.2017
comment
есть ли причина, по которой это полностью цитируется? есть источник? - person Xerus; 07.06.2017

У меня, вероятно, было бы 2 метода: один для возврата Collection и один для возврата коллекции как Stream.

class Team
{
    private List<Player> players = new ArrayList<>();

// ...

    public List<Player> getPlayers()
    {
        return Collections.unmodifiableList(players);
    }

    public Stream<Player> getPlayerStream()
    {
        return players.stream();
    }

}

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

Это также добавляет еще 1 метод к вашему API, поэтому у вас не будет слишком много методов.

person dkatzel    schedule 10.07.2014
comment
Потому что он хотел выбрать между этими двумя вариантами и спросил плюсы и минусы каждого из них. Более того, это дает каждому лучшее понимание этих концепций. - person Libert Piou Piou; 11.07.2014
comment
Пожалуйста, не делай этого. Представьте себе API! - person François Gautier; 08.11.2016