Рубрики
Без рубрики

Возвращающийся поток против Коллекция

Автор оригинала: Anshul Bansal.

1. Обзор

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

В этой статье мы рассмотрим Stream и Collection и обсудим различные сценарии, соответствующие их использованию.

2. Сбор против Течение

Java Collection s предлагает эффективные механизмы для хранения и обработки данных, предоставляя такие структуры данных , как List , Set и Map .

Однако API потока полезен для выполнения различных операций с данными без необходимости промежуточного хранения. Таким образом, Поток работает аналогично прямому доступу к данным из базового хранилища, такого как коллекции и Ресурсы ввода-вывода/|.

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

Хотя Java позволяет легко конвертировать из Collection в Stream и наоборот, удобно знать, какой механизм является наилучшим для визуализации/обработки результирующего набора.

Например, мы можем преобразовать Коллекцию в поток с помощью методов stream и parallelStream :

public Stream userNames() {
    ArrayList userNameSource = new ArrayList<>();
    userNameSource.add("john");
    userNameSource.add("smith");
    userNameSource.add("tom");
    return userNames.stream();
}

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

public List userNameList() {
    return userNames().collect(Collectors.toList());
}

Здесь мы преобразовали поток в список с помощью коллекторов.Метод ToList (). Аналогично, мы можем преобразовать поток в набор или в карту:

public static Set userNameSet() {
    return userNames().collect(Collectors.toSet());
}

public static Map userNameMap() {
    return userNames().collect(Collectors.toMap(u1 -> u1.toString(), u1 -> u1.toString()));
}

3. Когда возвращать поток?

3.1. Высокая Стоимость Материализации

Stream API предлагает ленивое выполнение и фильтрацию результатов на ходу-наиболее эффективные способы снижения затрат на материализацию.

Например, метод ReadAllLines в классе Java NIO Files отображает все строки файла, для которых JVM должна хранить в памяти все содержимое файла. Таким образом, этот метод имеет высокую стоимость материализации, связанную с возвратом списка строк.

Однако класс Files также предоставляет метод lines, который возвращает поток , который мы можем использовать для визуализации всех строк или даже лучше ограничить размер результирующего набора с помощью метода limit – оба с ленивым выполнением:

Files.lines(path).limit(10).collect(toList());

Кроме того, Поток не выполняет промежуточные операции, пока мы не вызовем терминальные операции, такие как forEach над ним:

userNames().filter(i -> i.length() >= 4).forEach(System.out::println);

Таким образом, a Stream позволяет избежать затрат, связанных с преждевременной материализацией.

3.2. Большой или Бесконечный результат

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

Кроме того, в случае бесконечных результатов мы обычно не обрабатываем весь набор результатов. Таким образом, встроенные функции Stream API, такие как filter и limit , оказываются удобными при обработке желаемого набора результатов, что делает Stream предпочтительным выбором.

3.3. Гибкость

Stream s очень гибки, позволяя обрабатывать результаты в любой форме или порядке.

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

Например, мы можем фильтровать/упорядочивать/ограничивать результаты, используя различные операции, доступные в потоковом API:

public static Stream filterUserNames() {
    return userNames().filter(i -> i.length() >= 4);
}

public static Stream sortUserNames() {
    return userNames().sorted();
}

public static Stream limitUserNames() {
    return userNames().limit(3);
}

3.4. Функциональное поведение

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

Например, давайте отфильтруем и ограничим набор результатов, полученных из основного потока :

userNames().filter(i -> i.length() >= 4).limit(3).forEach(System.out::println);

Здесь такие операции, как filter и limit в потоке , каждый раз возвращают новый Поток и не изменяют источник потока , предоставленный методом userNames .

4. Когда возвращать коллекцию?

4.1. Низкая Стоимость Материализации

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

Другими словами, Java создает Коллекцию с нетерпением, вычисляя все элементы в начале. Следовательно, Коллекция с большим набором результатов оказывает большое давление на память кучи при материализации.

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

4.2. Фиксированный Формат

Мы можем использовать Collection для обеспечения согласованного набора результатов для пользователя. Например, Collection s, такие как TreeSet и TreeMap , возвращают естественно упорядоченные результаты.

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

4.3. Повторно Используемый Результат

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

public static void tryStreamTraversal() {
    Stream userNameStream = userNames();
    userNameStream.forEach(System.out::println);
    
    try {
        userNameStream.forEach(System.out::println);
    } catch(IllegalStateException e) {
        System.out.println("stream has already been operated upon or closed");
    }
}

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

4.4. Модификация

A Collection , в отличие от a Stream , позволяет изменять элементы, например добавлять или удалять элементы из источника результатов. Следовательно, мы можем рассмотреть возможность использования коллекций для возврата результирующего набора, чтобы разрешить изменения потребителем.

Например, мы можем изменить ArrayList с помощью add |//remove методов:

userNameList().add("bob");
userNameList().add("pepper");
userNameList().remove(2);

Аналогично, такие методы, как put и remove позволяют изменять карту:

Map userNameMap = userNameMap();
userNameMap.put("bob", "bob");
userNameMap.remove("alfred");

4.5. Результат в памяти

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

5. Заключение

В этой статье мы сравнили Поток с Коллекция и рассмотрели различные сценарии, которые им подходят.

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

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

Как обычно, исходный код доступен на GitHub .