1. Обзор
В этом руководстве мы рассмотрим различные методы получения списка
из потока
. Мы также обсудим различия между ними и когда какой метод использовать.
2. Сбор элементов потока в список
Получение списка
из потока
— наиболее часто используемая терминальная операция конвейера
потока . До Java 16 мы использовали метод Stream.collect()
и передавали его коллектору
в качестве аргумента для сбора элементов. Сам коллектор
был создан путем вызова метода Collectors.toList() .
Однако были запросы на изменение метода для получения списка
непосредственно из экземпляра Stream .
После выпуска Java 16 теперь мы можем вызывать toList()
, новый метод непосредственно в Stream
, чтобы получить List
. Такие библиотеки, как StreamEx,
также предоставляют удобный способ получить список
непосредственно из потока
.
Мы можем накапливать элементы Stream в
список
, используя:
Stream.collect(Collectors.toList())
: Начиная с Java 8Stream.collect( Collectors.toUnmodifiableList() )
: начиная с Java 10Stream.toList()
: начиная с Java 16
Мы будем работать с методами в хронологическом порядке их выпуска.
3. Анализ списков
Давайте сначала создадим списки из методов, описанных в предыдущем разделе. После этого проанализируем их свойства.
Мы будем использовать следующий поток
кодов стран для всех примеров:
Stream.of(Locale.getISOCountries());
3.1. Создание списков
Теперь мы создадим список
из заданного потока
кодов стран, используя различные методы:
Во-первых, давайте создадим список
, используя Collectors:toList()
:
List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toList());
После этого соберем его с помощью Collectors.toUnmodifiedList()
:
List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toUnmodifiableList());
Вот в этих методах мы накапливаем Stream
в List
через интерфейс Collector
.
Это приводит к дополнительному выделению и копированию, поскольку мы не работаем напрямую с потоком.
Затем повторим сбор с помощью Stream.toList()
:
List<String> result = Stream.of(Locale.getISOCountries()).toList();
Здесь мы получаем список
непосредственно из потока,
что предотвращает дополнительное выделение и копирование.
Таким образом, использование toList()
непосредственно в потоке
является более кратким, аккуратным, удобным и оптимальным по сравнению с двумя другими вызовами.
3.2. Изучение накопленных списков
Давайте начнем с изучения типа списка
, который мы создали.
Collectors.toList()
собирает элементы Stream в
ArrayList
:
java.util.ArrayList
Collectors.toUnmodifiedList()
собирает элементы Stream
в неизменяемый список
.
java.util.ImmutableCollections.ListN
Stream.toList()
собирает элементы в неизменяемый список
.
java.util.ImmutableCollections.ListN
Хотя текущая реализация Collectors.toList()
создает изменяемый List
, сама спецификация метода не гарантирует тип, изменяемость, сериализуемость или потокобезопасность List.
С другой стороны, как Collectors.toUnmodifiableList()
, так и Stream.toList()
создают неизменяемые списки.
Это означает, что мы можем выполнять такие операции, как добавление и сортировка элементов Collectors.toList(),
но не элементы Collectors.toUnmodifiableList()
и Stream.toList()
.
3.3. Разрешение пустых элементов в списках
Хотя Stream.toList()
создает неизменяемый список
, он все же не совпадает с Collectors.toUnmodifiableList().
Это связано с тем, что Stream.toList()
допускает нулевые
элементы, а Collectors.toUnmodifiableList()
не допускает нулевые
элементы. Однако Collectors.toList()
допускает нулевые
элементы.
Collectors.toList()
не генерирует исключение
при сборе потока
, содержащего нулевые
элементы:
Assertions.assertDoesNotThrow(() -> {
Stream.of(null,null).collect(Collectors.toList());
});
Collectors.toUnmodifiedList()
генерирует исключение NulPointerException
, когда мы собираем поток
, содержащий нулевые
элементы:
Assertions.assertThrows(NullPointerException.class, () -> {
Stream.of(null,null).collect(Collectors.toUnmodifiableList());
});
Stream.toList()
не генерирует исключение NulPointerException
, когда мы пытаемся собрать поток
, содержащий нулевые
элементы:
Assertions.assertDoesNotThrow(() -> {
Stream.of(null,null).toList();
});
Поэтому при переносе нашего кода с Java 8 на Java 10 или Java 16 следует обращать на это внимание. Мы не можем слепо использовать Stream.toList()
вместо Collectors.toList()
или Collectors.toUnmodifiableList().
3.4. Резюме анализа
В следующей таблице приведены различия и сходства списков из нашего анализа:
4. Когда использовать разные методы toList()
Основная цель добавления Stream.toList()
— уменьшить многословность API Collector
.
Как было показано ранее, использование методов
Collectors
для получения List
очень многословно. С другой стороны, использование метода Stream.toList()
делает код аккуратным и лаконичным.
Тем не менее, как было показано в предыдущих разделах, Stream.toList()
нельзя использовать в качестве ярлыка для Collectors.toList()
или Collectors.toUnmodifiableList()
.
Во-вторых, Stream.toList()
использует меньше памяти, поскольку его реализация не зависит от интерфейса Collector
. Он накапливает элементы Stream
непосредственно в List
. Итак, если мы заранее знаем размер потока, оптимальным будет использование Stream.toList().
В-третьих, мы знаем, что Stream
API предоставляет реализацию только для метода toList()
. Он не содержит аналогичных методов для получения карты или набора. Итак, если нам нужен единый подход для получения любых преобразователей, таких как список, карта или набор, мы продолжим использовать Collector
API. Это также позволит сохранить последовательность и избежать путаницы.
Наконец, если мы используем версии ниже Java 16, мы должны продолжать использовать методы Collectors .
В следующей таблице приведены оптимальные варианты использования данных методов:
5. Вывод
В этой статье мы разобрали три самых популярных способа получения списка
из потока
. Затем мы рассмотрели основные различия и сходства. И мы также обсудили, как и когда использовать эти методы.
Как всегда, исходный код примеров, используемых в этой статье, доступен на GitHub .