Предположим, у меня есть метод, который возвращает представление только для чтения в список участников:
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? Были ли потоки предназначены для того, чтобы всегда быть «завершенными» внутри одного и того же выражения, в котором они были созданы?
java
collections
java-8
encapsulation
java-stream
fredoverflow
источник
источник
players.stream()
именно такой метод возвращает поток вызывающей стороне. Реальный вопрос в том, действительно ли вы хотите ограничить вызывающего абонента одним обходом, а также лишить его доступа к вашей коллекции черезCollection
API? Может, звонящий просто хочет, чтобыaddAll
он попал в другую коллекцию?Ответы:
Ответ, как всегда, «это зависит». Это зависит от того, насколько большой будет возвращенная коллекция. Это зависит от того, изменяется ли результат с течением времени, и насколько важна согласованность возвращаемого результата. И это очень сильно зависит от того, как пользователь может использовать ответ.
Во-первых, обратите внимание, что вы всегда можете получить коллекцию из потока, и наоборот:
Таким образом, вопрос в том, что более полезно для ваших абонентов.
Если ваш результат может быть бесконечным, есть только один выбор: поток.
Если ваш результат может быть очень большим, вы, вероятно, предпочтете Stream, так как не может быть никакой ценности в его одновременной реализации, и это может создать значительное давление в куче.
Если все, что собирается сделать вызывающий объект, - это перебрать его (поиск, фильтр, агрегат), вы должны предпочесть Stream, так как Stream уже имеет эти встроенные функции, и нет необходимости материализовать коллекцию (особенно, если пользователь может не обрабатывать Весь результат.) Это очень распространенный случай.
Даже если вы знаете, что пользователь будет повторять его несколько раз или иным образом хранить его, вы все равно можете захотеть вернуть Stream вместо этого, потому что тот факт, что независимо от выбранной вами коллекции (например, ArrayList), может не быть форма, которую они хотят, и тогда звонящий должен все равно скопировать это. если вы вернете поток, они могут сделать
collect(toCollection(factory))
и получить его именно в той форме, которую они хотят.Вышеупомянутые случаи «предпочтения Stream» в основном происходят из-за того, что Stream более гибок; вы можете позднее связать с тем, как вы используете его, не неся при этом затрат и ограничений, связанных с его материализацией в Коллекцию.
Единственный случай, когда вы должны вернуть коллекцию, - это когда существуют строгие требования согласованности, и вы должны создать согласованный снимок движущейся цели. Затем вы захотите поместить элементы в коллекцию, которая не изменится.
Поэтому я бы сказал, что в большинстве случаев Stream является правильным ответом - он более гибкий, он не влечет за собой обычно ненужных затрат на материализацию и может быть легко превращен в коллекцию по вашему выбору, если это необходимо. Но иногда вам может понадобиться вернуть коллекцию (скажем, из-за строгих требований согласованности), или вы можете захотеть вернуть коллекцию, потому что вы знаете, как ее будет использовать пользователь, и знаете, что это наиболее удобно для них.
источник
У меня есть несколько моментов, чтобы добавить к отличному ответу Брайана Гетца .
Весьма распространено возвращать Stream из вызова метода в стиле «getter». Смотрите страницу использования Stream в Java 8 javadoc и ищите «методы ... которые возвращают Stream» для пакетов, отличных от
java.util.Stream
. Эти методы обычно используются в классах, которые представляют или могут содержать несколько значений или совокупностей чего-либо. В таких случаях API обычно возвращают коллекции или массивы из них. По всем причинам, которые Брайан отметил в своем ответе, очень гибко добавить сюда методы, возвращающие поток. Многие из этих классов уже имеют методы, возвращающие коллекции или массивы, потому что классы предшествуют API Streams. Если вы разрабатываете новый API, и имеет смысл предоставить методы, возвращающие поток, возможно, нет необходимости добавлять методы, возвращающие коллекцию.Брайан упомянул стоимость «материализации» ценностей в коллекцию. Чтобы усилить этот момент, на самом деле здесь есть две затраты: стоимость хранения значений в коллекции (выделение памяти и копирование), а также стоимость создания значений в первую очередь. Последнюю стоимость часто можно уменьшить или избежать, воспользовавшись ленивым поведением Stream. Хорошим примером этого являются API в
java.nio.file.Files
:Мало того, что
readAllLines
он должен хранить все содержимое файла в памяти, чтобы сохранить его в списке результатов, он также должен прочитать файл до самого конца, прежде чем он вернет список.lines
Метод может вернуть почти сразу же после того, как он выполнил некоторые настройки, оставляя чтение файла и разбивая строчки , пока позже , когда это необходимо - или нет вообще. Это огромное преимущество, если, например, звонящий интересуется только первыми десятью строками:Конечно, можно сэкономить значительное место в памяти, если вызывающий фильтр фильтрует поток так, чтобы он возвращал только строки, соответствующие шаблону и т. Д.
Идиома, которая, кажется, появляется, состоит в том, чтобы называть методы, возвращающие поток, после множественного числа имен вещей, которые оно представляет или содержит, без
get
префикса. Кроме того, хотяstream()
это разумное имя для метода, возвращающего поток, когда существует только один возможный набор значений, которые должны быть возвращены, иногда существуют классы, которые имеют совокупности значений нескольких типов. Например, предположим, у вас есть какой-то объект, который содержит как атрибуты, так и элементы. Вы можете предоставить два API, возвращающих поток:источник
Вот как они используются в большинстве примеров.
Примечание: возврат потока не так уж отличается от возврата итератора (допускается с гораздо большей выразительностью)
ИМХО, лучшее решение - заключить в капсулу, почему вы это делаете, а не возвращать коллекцию.
например
или если вы собираетесь их посчитать
источник
Если поток конечен, и на возвращаемых объектах есть ожидаемая / нормальная операция, которая вызовет проверенное исключение, я всегда возвращаю коллекцию. Потому что, если вы собираетесь что-то делать с каждым из объектов, которые могут вызвать исключение проверки, вы будете ненавидеть поток. Один реальный недостаток потоков - невозможность элегантно работать с проверенными исключениями.
Возможно, это признак того, что вам не нужны проверенные исключения, что справедливо, но иногда они неизбежны.
источник
В отличие от коллекций, потоки имеют дополнительные характеристики . Поток, возвращаемый любым методом, может быть:
Эти различия также существуют в коллекциях, но там они являются частью очевидного контракта:
Для потребителя потока (из метода return или в качестве параметра метода) это опасная и запутанная ситуация. Чтобы убедиться, что их алгоритм работает правильно, потребители потоков должны убедиться, что алгоритм не ошибается в предположении о характеристиках потока. И это очень сложно сделать. В модульном тестировании это будет означать, что вы должны умножить все ваши тесты, которые будут повторяться, с тем же содержимым потока, но с потоками, которые
Написание метода защищает потоки, которые генерируют исключение IllegalArgumentException, если у входного потока есть характеристики, нарушающие ваш алгоритм, сложно, потому что свойства скрыты.
Это оставляет Stream только в качестве допустимого выбора в сигнатуре метода, когда ни одна из проблем выше не имеет значения, что редко имеет место.
Гораздо безопаснее использовать другие типы данных в сигнатурах методов с явным контрактом (и без неявной обработки пула потоков), что делает невозможным случайную обработку данных с неправильными предположениями об упорядоченности, размерности или параллельности (и использовании пула потоков).
источник
Я думаю, это зависит от вашего сценария. Может быть, если вы делаете свой
Team
инструментIterable<Player>
, этого достаточно.или в функциональном стиле:
Но если вы хотите более полный и свободный API, поток может быть хорошим решением.
источник
stream.count()
довольно простой), но это число не имеет большого значения ни для чего, кроме отладки или оценки.Хотя некоторые из наиболее влиятельных респондентов давали отличные общие советы, я удивлен, что никто так и не сказал:
Если у вас уже есть «материализованный» под
Collection
рукой (то есть он уже был создан до вызова - как в данном примере, где это поле члена), нет смысла преобразовывать его в aStream
. Звонящий может легко сделать это самостоятельно. Принимая во внимание, что если вызывающая сторона хочет использовать данные в их первоначальном виде, вы преобразуете их в силу,Stream
заставляющую их выполнять избыточную работу для повторной материализации копии исходной структуры.источник
источник
Вероятно, у меня было бы 2 метода: один для возврата
Collection
а другой для возврата коллекции в видеStream
.Это лучшее из обоих миров. Клиент может выбрать, хотят ли они Список или Поток, и ему не нужно делать дополнительное создание объекта, создавая неизменную копию списка, просто чтобы получить Поток.
Это также добавляет только 1 метод к вашему API, чтобы у вас не было слишком много методов.
источник