Должен ли я возвращать коллекцию или stream?
Предположим, у меня есть метод, который возвращает вид, ansible только для чтения, в список участников:
class Team { private List players = new ArrayList(); // ... public List getPlayers() { return Collections.unmodifiableList(players); } }
Кроме того, предположим, что все, что делает клиент, сразу перебирает список. Возможно, чтобы игроки попали в JList или что-то в этом роде. Клиент не сохраняет ссылку на список для последующей проверки!
Учитывая этот общий сценарий, должен ли я возвращать stream вместо этого?
- Что более эффективно, для каждого цикла или для iteratorа?
- Как сортировать аррайалист объектов по свойству?
- Легкий способ изменить Iterable в коллекцию
- Быстрее добавлять в коллекцию, сортировать ее или добавлять в сортированную коллекцию?
- Использование '? расширяет 'и'? супер 'в compilationе generics
public Stream getPlayers() { return players.stream(); }
Или возвращает stream, не идиоматический в Java? Были ли streamи, которые всегда были «завершены» внутри того же выражения, в котором они были созданы?
- Java 8: производительность streamов и коллекций
- Удаление элементов из коллекции во время итерации
- Как подсчитать количество вхождений элемента в список
- Почему в Java нет SortedList?
- Самый простой способ объединить два списка в карту (Java)?
- Максимальный размер HashSet, Vector, LinkedList
- Как выполнить поиск в списке объектов Java
- Java 8 Различают по свойству
Ответ, как всегда, «это зависит». Это зависит от того, насколько большой будет возвращенная коллекция. Это зависит от того, изменяется ли результат со временем и насколько важна согласованность возвращаемого результата. И это сильно зависит от того, как пользователь может использовать ответ.
Во-первых, обратите внимание, что вы всегда можете получить коллекцию из streamа, и наоборот:
// If API returns Collection, convert with stream() getFoo().stream()... // If API returns Stream, use collect() Collection c = getFooStream().collect(toList());
Поэтому вопрос в том, что более полезно для ваших абонентов.
Если ваш результат может быть бесконечным, есть только один выбор: Stream.
Если ваш результат может быть очень большим, вы, вероятно, предпочитаете Stream, так как не может быть никакой ценности для материализации всего этого, и это может создать значительное давление кучи.
Если все вызывающие вызовы будут выполняться, итерация через него (поиск, фильтрация, агрегат), вы должны предпочесть Stream, поскольку Stream уже имеет эти встроенные компоненты, и нет необходимости в материализации коллекции (особенно если пользователь не может обработать весь результат.) Это очень распространенный случай.
Даже если вы знаете, что пользователь будет повторять его несколько раз или иным образом держать его вокруг, вы все равно можете захотеть вернуть Stream вместо этого, поскольку тот факт, что какая-либо коллекция, которую вы выбрали для ее размещения (например, ArrayList), может не быть формы, которую они хотят, а затем вызывающий должен ее скопировать. если вы collect(toCollection(factory))
stream, они могут collect(toCollection(factory))
и получать его в той форме, в которой они хотят.
Вышеупомянутые случаи «предпочитают stream» в основном вытекают из того, что Stream более гибкий; вы можете опоздать на то, как вы используете его, не прибегая к издержкам и ограничениям для его воплощения в коллекцию.
Один случай, когда вы должны вернуть коллекцию, – это когда есть сильные требования к согласованности, и вы должны создать согласованный снимок движущейся цели. Затем вам нужно будет поместить элементы в коллекцию, которая не изменится.
Поэтому я бы сказал, что большую часть времени Stream – правильный ответ – он более гибкий, он не налагает обычно ненужные затраты на материализацию и может быть легко превращен в коллекцию по вашему выбору, если это необходимо. Но иногда вам, возможно, придется возвращать коллекцию (скажем, из-за сильных требований согласованности), или вы можете захотеть вернуть Collection, потому что знаете, как пользователь будет ее использовать и знать, что это самое удобное для них.
У меня есть несколько моментов, чтобы добавить отличный ответ Брайана Гетца .
Очень часто возвращать stream из метода вызова метода «getter». См. Страницу использования streamа в Java 8 javadoc и найдите «методы … которые возвращают stream» для пакетов, отличных от java.util.Stream
. Эти методы обычно относятся к classам, которые представляют или могут содержать несколько значений или агрегатов чего-либо. В таких случаях API обычно возвращают коллекции или массивы из них. По всем причинам, которые Брайан отметил в своем ответе, очень гибко добавлять сюда методы Stream-return. У многих из этих classов уже есть методы, основанные на коллекции или массиве, поскольку classы предшествуют API Streams. Если вы разрабатываете новый API, и имеет смысл предоставлять методы Stream-return, возможно, нет необходимости добавлять методы возврата коллекции.
Брайан упомянул стоимость «материализации» ценностей в коллекцию. Чтобы усилить этот момент, на самом деле существуют две затраты: стоимость хранения значений в коллекции (выделение и копирование памяти), а также стоимость создания значений в первую очередь. Последнюю стоимость часто можно уменьшить или избежать, воспользовавшись стремлением Stream к лени. Хорошим примером этого являются API-интерфейсы в java.nio.file.Files
:
static Stream lines(path) static List readAllLines(path)
Не только readAllLines
должны хранить содержимое всего файла в памяти, чтобы сохранить его в списке результатов, он также должен прочитать файл до самого конца, прежде чем он вернет список. Метод lines
может вернуться почти сразу после того, как он выполнил некоторую настройку, оставив чтение файла и разрыв строки до тех пор, пока это будет необходимо – или совсем нет. Это огромная выгода, если, например, вызывающая сторона заинтересована только в первых десяти строках:
try (Stream lines = Files.lines(path)) { List firstTen = lines.limit(10).collect(toList()); }
Конечно, значительное пространство памяти может быть сохранено, если вызывающий фильтр фильтрует stream, чтобы возвращать только строки, соответствующие шаблону и т. Д.
Идиомой, которая, кажется, возникает, является имя методов возврата streamа после множественного числа имени того, что он представляет или содержит, без префикса get
. Кроме того, хотя stream()
является разумным именем для метода возврата streamа, когда есть только один возможный набор значений, которые должны быть возвращены, иногда есть classы, которые имеют агрегаты нескольких типов значений. Например, предположим, что у вас есть объект, содержащий как атрибуты, так и элементы. Вы можете предоставить два API-интерфейса, возвращающих stream:
Stream attributes(); Stream elements();
В контексте команды / предприятия / многоразовых classов:
Обычно разрабатывайте classы, не принимая во внимание то, что клиенты могут принять за class. Если вы вернете stream, вы вернете потенциально бесконечный список, и клиенты должны рассматривать это как потенциально бесконечный список. Это нарушение безопасности кода и хороший вкус для любого другого classа или другого метода для запуска stream.collect(toList())
в streamе, который он не создал сам, слепо предполагая, что он будет конечным. Если это был желательный шаблон кода, streamи должны иметь некоторый метод, например isFinite()
для защиты от ошибочных предположений.
Еще хуже то, что возврат streamа во избежание материализации одинаково плох, потому что кажется слишком легким материализовать stream с .collect()
, что означает, что class обслуживания избегает материализации, у classа клиента может возникнуть соблазн сделать это с высокой стоимостью, в особенно если вы постоянно возвращаете streamи в качестве стиля кода и больше не можете сказать, какой stream возвращается, должны быть материализованы, а какие – нет. Классические интерфейсы, которые указывают на плохую материализацию, – это java.util.Iterator (хотя commons-lang определяет IteratorUtils.toList ()).
Поэтому я бы рекомендовал использовать любой метод не-сбора данных только тогда, когда вы хотите документировать своим клиентам, что результат может быть бесконечным или может быть непригоден для материализации.
То, что streamи более удобны для функционального программирования, не означает, что они должны быть предпочтительнее, это означает, что JDK должен расширять свой API-интерфейс коллекции, чтобы допускать одни и те же операции, поэтому клиентам не нужно вызывать collection.stream()....
,
Для домашних заданий, строго личных проектов или другого неиспользованного кода
С простой алгоритмической точки зрения, игнорируя стиль кода, читаемость или проблемы OO-Design, предпочитая Streams, как в принятом ответе, это нормально. Но я не вижу, как это можно сделать общей рекомендацией по StackOverflow
Были ли streamи, которые всегда были «завершены» внутри того же выражения, в котором они были созданы?
Именно так они используются в большинстве примеров.
Примечание: возврат streamа не отличается от возвращения Iterator (допускается с гораздо большей выразительностью)
ИМХО лучшее решение заключается в том, чтобы инкапсулировать, почему вы это делаете, а не возвращать коллекцию.
например
public int playerCount(); public Player player(int n);
или если вы намереваетесь их подсчитать
public int countPlayersWho(Predicate test);
Если stream конечен и на возвращенных объектах есть ожидаемая / нормальная операция, которая будет вызывать проверенное исключение, я всегда возвращаю коллекцию. Потому что, если вы собираетесь что-то делать на каждом из объектов, которые могут вызывать исключение проверки, вы будете ненавидеть stream. Один настоящий недостаток в streamах – я не могу справиться с проверенными исключениями изящно.
Теперь, возможно, это признак того, что вам не нужны проверенные исключения, что справедливо, но иногда они неизбежны.
Я думаю, это зависит от вашего сценария. Может быть, если вы создадите свою Team
Iterable
, этого достаточно.
for (Player player : team) { System.out.println(player); }
или в функциональном стиле:
team.forEach(System.out::println);
Но если вам нужен более полный и плавный api, stream может быть хорошим решением.
Возможно, завод Stream станет лучшим выбором. Большая победа только разоблачения коллекций через Stream заключается в том, что он лучше инкапсулирует структуру данных вашей модели домена. Невозможно, чтобы любое использование ваших classов домена влияло на внутреннюю работу вашего списка или набора, просто подвергая stream.
Он также поощряет пользователей вашего classа домена писать код в более современном стиле Java 8. Можно поэтапно реорганизовать этот стиль, сохранив ваши существующие геттеры и добавив новые получатели Stream-возвращающихся. Со временем вы можете переписать свой устаревший код до тех пор, пока не удалите все получатели, которые возвращают список или набор. Такой рефакторинг действительно хорош, как только вы очистите все устаревшие коды!
У меня, вероятно, было бы 2 метода, один для возврата Collection
и один для возврата коллекции в виде Stream
.
class Team { private List players = new ArrayList<>(); // ... public List getPlayers() { return Collections.unmodifiableList(players); } public Stream getPlayerStream() { return players.stream(); } }
Это лучшее из обоих миров. Клиент может выбрать, хотят ли они List или Stream, и им не нужно делать создание дополнительных объектов для создания неизменной копии списка, чтобы получить Stream.
Это также добавляет еще 1 метод к вашему API, поэтому у вас не слишком много методов