Перейти к основному содержимому

Соберите поток Java в неизменяемую коллекцию

· 3 мин. чтения

1. Введение

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

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

2. Использование стандартной Java

2.1. Использование toUnmodifiedList в Java

Начиная с Java 10, мы можем использовать метод toUnmodifiableList из класса Collectors Java:

List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
.collect(toUnmodifiableList());

Используя этот метод, мы получаем реализацию List , которая не поддерживает нулевые значения из ImmutableCollections Java :

class java.util.ImmutableCollections$ListN

2.2. Использование Java collectAndThen

Метод collectAndThen из класса Collectors в Java принимает Collector и Finisher Function . Этот финишер применяется к результату, возвращаемому сборщиком:

List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
.collect(collectingAndThen(toList(), ImmutableList::copyOf));

System.out.println(result.getClass());

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

2.3. Использование метода Stream.toList()

Java 16 представляет новый метод Stream API, который называется toList(). Этот удобный метод возвращает неизменяемый список , содержащий элементы потока :

@Test
public void whenUsingStreamToList_thenReturnImmutableList() {
List<String> immutableList = Stream.of("a", "b", "c", "d").toList();

Assertions.assertThrows(UnsupportedOperationException.class, () -> {
immutableList.add("e");
});
}

Как видно из модульного теста, Stream.toList() возвращает неизменяемый список . Таким образом, попытка добавить новый элемент в список просто приведет к UnsupportedOperationException .

Имейте в виду, что новый метод Stream.toList() немного отличается от существующего метода Collectors.toList() , поскольку он возвращает неизменяемый список.

3. Создание собственного коллектора

У нас также есть возможность реализовать собственный Collector .

3.1. Базовый неизменяемый коллектор

Для этого мы можем использовать статический метод Collector.of :

public static <T> Collector<T, List<T>, List<T>> toImmutableList() {
return Collector.of(ArrayList::new, List::add,
(left, right) -> {
left.addAll(right);
return left;
}, Collections::unmodifiableList);
}

Мы можем использовать эту функцию так же, как любой встроенный Collector :

List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
.collect(MyImmutableListCollector.toImmutableList());

Наконец, давайте проверим тип вывода:

class java.util.Collections$UnmodifiableRandomAccessList

3.2. Создание универсального объекта MyImmutableListCollector

У нашей реализации есть одно ограничение — она всегда возвращает неизменяемый экземпляр, поддерживаемый ArrayList . Однако с небольшим улучшением мы можем заставить этот сборщик возвращать указанный пользователем тип:

public static <T, A extends List<T>> Collector<T, A, List<T>> toImmutableList(
Supplier<A> supplier) {

return Collector.of(
supplier,
List::add, (left, right) -> {
left.addAll(right);
return left;
}, Collections::unmodifiableList);
}

Итак, теперь вместо определения Поставщика в реализации метода мы запрашиваем Поставщика у пользователя:

List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
.collect(MyImmutableListCollector.toImmutableList(LinkedList::new));

Кроме того, мы используем LinkedList вместо ArrayList .

class java.util.Collections$UnmodifiableList

На этот раз мы получили UnmodifiableList вместо UnmodifiedRandomAccessList .

4. Использование коллектора Guava

В этом разделе мы собираемся использовать библиотеку Google Guava для управления некоторыми из наших примеров:

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>

Начиная с Guava 21, каждый неизменяемый класс поставляется с сопутствующим Collector , который так же прост в использовании, как и стандартный Collector s в Java :

List<Integer> list = IntStream.range(0, 9)
.boxed()
.collect(ImmutableList.toImmutableList());

Результирующий экземпляр — это RegularImmutableList :

class com.google.common.collect.RegularImmutableList

5. Вывод

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

Как всегда, полный исходный код этой статьи находится на GitHub. Они разделены по версии Java на примеры для разделов 3-4 , раздела 2.2 и раздела 2.3 .