Как я могу собрать поток Java 8 в Guava ImmutableCollection?

82

Я бы хотел сделать следующее:

List<Integer> list = IntStream.range(0, 7).collect(Collectors.toList());

но в том смысле, что полученный список является реализацией Guava ImmutableList.

Я знаю, что могу сделать

List<Integer> list = IntStream.range(0, 7).collect(Collectors.toList());
List<Integer> immutableList = ImmutableList.copyOf(list);

но я хотел бы собирать к нему напрямую. я пытался

List<Integer> list = IntStream.range(0, 7)
    .collect(Collectors.toCollection(ImmutableList::of));

но это вызвало исключение:

java.lang.UnsupportedOperationException в com.google.common.collect.ImmutableCollection.add (ImmutableCollection.java:96)

Золтан
источник

Ответы:

89

toImmutableList()Метод в принятом ответе Алексиса теперь включен в гуавах 21 и может быть использован в качестве:

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

Edit: Удалены @Betaиз ImmutableList.toImmutableListнаряду с другими часто используемыми API , в версии 27.1 ( 6242bdd ).

Ритеш
источник
1
Метод, помеченный как @Beta. Значит, это не рекомендуется документами?
user2602807
По-прежнему @Betaс Guava 26.0.
Пер Лундберг
Для удобства Google в период с 2004 по 2009 год держал Gmail в рамках бета-тега, который уже был достаточно стабильным зрелым продуктом на момент запуска в 2004 году. Google в целом неохотно продвигает продукты со статусом бета. Почти до комедии.
anataliocs
68

Вот collectingAndThenтут и пригодится сборщик:

List<Integer> list = IntStream.range(0, 7).boxed()
                .collect(collectingAndThen(toList(), ImmutableList::copyOf));

Он применяет преобразование к Listтолько что построенному; в результате получается файл ImmutableList.


Или вы можете напрямую собрать в Builderи позвонить build()в конце:

List<Integer> list = IntStream.range(0, 7)
                .collect(Builder<Integer>::new, Builder<Integer>::add, (builder1, builder2) -> builder1.addAll(builder2.build()))
                .build();

Если эта опция кажется вам многословной и вы хотите использовать ее во многих местах, вы можете создать свой собственный сборщик:

class ImmutableListCollector<T> implements Collector<T, Builder<T>, ImmutableList<T>> {
    @Override
    public Supplier<Builder<T>> supplier() {
        return Builder::new;
    }

    @Override
    public BiConsumer<Builder<T>, T> accumulator() {
        return (b, e) -> b.add(e);
    }

    @Override
    public BinaryOperator<Builder<T>> combiner() {
        return (b1, b2) -> b1.addAll(b2.build());
    }

    @Override
    public Function<Builder<T>, ImmutableList<T>> finisher() {
        return Builder::build;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return ImmutableSet.of();
    }
}

а потом:

List<Integer> list = IntStream.range(0, 7)
                              .boxed()
                              .collect(new ImmutableListCollector<>());

На всякий случай ссылка в комментариях пропадает; мой второй подход может быть определен в статическом служебном методе, который просто использует Collector.of. Это проще, чем создавать свой собственный Collectorкласс.

public static <T> Collector<T, Builder<T>, ImmutableList<T>> toImmutableList() {
    return Collector.of(Builder<T>::new, Builder<T>::add, (l, r) -> l.addAll(r.build()), Builder<T>::build);
}

и использование:

 List<Integer> list = IntStream.range(0, 7)
                               .boxed()
                               .collect(toImmutableList());
Алексис С.
источник
3
Это все еще создает промежуточный список, не так ли? Я бы хотел этого избежать. Может ImmutableList.Builderбыть какая-нибудь помощь?
Zoltán
4
@ Zoltán Вы можете накапливать значения непосредственно в построителе (см. Редактирование), а затем вызывать build().
Alexis C.
4
Спасибо за подробный ответ. Похоже, что в настоящее время этот вопрос решается: github.com/google/guava/issues/1582 , здесь также есть хороший пример (очень похожий на то, что вы предложили): gist.github.com/JakeWharton/9734167
Zoltán
4
@ Золтан Ах да; хорошие находки; он просто превращает вторую альтернативу в служебные методы. Немного лучше, чем определять свой собственный Collectorкласс :-)
Alexis C.
Ссылочные типы могут быть ImmutableList<Integer>(вместо List<Integer>).
palacsint
17

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

Stream<Integer> stream = IntStream.range(0, 7).boxed();
List<Integer> list = ImmutableList.copyOf(stream.iterator());

Источник .

Золтан
источник
6

Кстати: начиная с JDK 10 это можно сделать на чистой Java:

List<Integer> list = IntStream.range(0, 7)
    .collect(Collectors.toUnmodifiableList());

Также toUnmodifiableSetи в toUnmodifiableMapналичии.

Внутри коллектора это было сделано через List.of(list.toArray())

Григорий Кислин
источник
1
Это не совсем так, поскольку ImmutableCollections.List12and ImmutableCollections.ListN! = Guava's ImmutableList. С практической точки зрения вы в основном правы, но все же имеет смысл упомянуть этот нюанс в своем ответе.
Пер Лундберг
4

К вашему сведению, есть разумный способ сделать это в Guava без Java 8:

ImmutableSortedSet<Integer> set = ContiguousSet.create(
    Range.closedOpen(0, 7), DiscreteDomain.integers());
ImmutableList<Integer> list = set.asList();

Если вам на самом деле не нужна Listсемантика и вы можете просто использовать a NavigableSet, это даже лучше, поскольку a ContiguousSetне обязательно хранить все элементы в нем (только Rangeи DiscreteDomain).

ColinD
источник