How can I collect a Java 8 stream into a Guava ImmutableCollection?

Java 8GuavaJava Stream

Java 8 Problem Overview


I would like to do the following:

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

but in a way that the resulting list is an implementation of Guava's ImmutableList.

I know I could do

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

but I would like to collect to it directly. I've tried

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

but it threw an exception:

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

Java 8 Solutions


Solution 1 - Java 8

The toImmutableList() method in the accepted answer of Alexis is now included in Guava 21 and can be used as:

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

Edit: Removed @Beta from ImmutableList.toImmutableList along with other frequently used APIs in Release 27.1 (6242bdd).

Solution 2 - Java 8

This is where the collectingAndThen collector is useful:

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

It applies the transformation to the List you just built; resulting in an ImmutableList.


Or you could directly collect into the Builder and call build() at the end:

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

If this option is a bit-verbose to you and you want to use it in many places, you can create your own collector:

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();
    }
}

and then:

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


Just in case the link disappears in the comments; my second approach could be defined in a static utility method that simply uses Collector.of. It's simpler than creating your own Collector class.

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);
}

and the usage:

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

Solution 3 - Java 8

While not a direct answer to my question (it does not use collectors), this is a fairly elegant approach which doesn't use intermediate collections:

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

Source.

Solution 4 - Java 8

BTW: since JDK 10 it can be done in pure Java:

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

Also toUnmodifiableSet and toUnmodifiableMap available.

Inside collector it was done via List.of(list.toArray())

Solution 5 - Java 8

FYI, there's a reasonable way to do this in Guava without Java 8:

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

If you don't actually need the List semantics and can just use a NavigableSet, that's even better since a ContiguousSet doesn't have to actually store all the elements in it (just the Range and DiscreteDomain).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZolt&#225;nView Question on Stackoverflow
Solution 1 - Java 8RiteshView Answer on Stackoverflow
Solution 2 - Java 8Alexis C.View Answer on Stackoverflow
Solution 3 - Java 8ZoltánView Answer on Stackoverflow
Solution 4 - Java 8Grigory KislinView Answer on Stackoverflow
Solution 5 - Java 8ColinDView Answer on Stackoverflow