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



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



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? Были потоки, предназначенные для того, чтобы всегда "завершаться" внутри того же выражения, в котором они были созданы?

650   8  

8 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

у меня есть несколько пунктов, чтобы добавить к отличный ответ Брайана Гетца.

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

Брайан упомянул стоимость "материализации" ценностей в коллекцию. Чтобы усилить этот момент, здесь фактически есть две затраты: стоимость хранения значений в коллекции (выделение памяти и копирование), а также стоимость создания значений в первую очередь. Последняя стоимость часто можно избежать, воспользовавшись потока лень поведением. Хорошим примером этого являются 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());
}

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

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

Stream<Attribute>  attributes();
Stream<Element>    elements();

в контексте team / enterprise / многоразовые классы:

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

еще хуже, возвращение потока, чтобы избежать материализации, одинаково плохо, потому что кажется слишком легко материализовать поток с .collect(), что означает, что в то время как обслуживающий класс избегает материализации, клиентский класс может соблазниться сделать это с высокими затратами, в частности, если вы все время возвращаете потоки в виде стиля кода и больше не можете сказать, какой поток должен быть материализован и какие из них не следует. Классические интерфейсы, которые намекают на плохую материализацию, - это java.утиль.Итератор (хотя commons-lang определяет IteratorUtils.toList ()) для.

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

то, что потоки более удобны для функционального программирования, не означает, что они должны быть предпочтительными, это означает, что JDK должен расширить свой API коллекции, чтобы разрешить те же операции, так что клиенты не должны вызывать collection.stream().....

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

С простой алгоритмической точки зрения, когда игнорируется стиль кода, читаемость или проблемы OO-дизайна, предпочтение потоков, как в принятом ответе, в порядке. Но я не вижу, как это можно сделать общей рекомендацией по StackOverflow

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

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

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

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

например

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

или если вы намерены их пересчитать

public int countPlayersWho(Predicate<? super Player> test);

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

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

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

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

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

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

но если вы хотите более полный и свободный api, поток может быть хорошим решением.

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

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

у меня, вероятно, есть 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, так что у вас не слишком много методов

Comments

    Ничего не найдено.