87

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)

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
Zoltán
  • 21,321
  • 14
  • 93
  • 134

5 Answers5

98

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).

Ritesh
  • 7,472
  • 2
  • 39
  • 43
  • 1
    The method marked as @Beta. So it's not reccomended apriory by docs ? – user2602807 Nov 12 '19 at 08:46
  • Still `@Beta` as of Guava 26.0. – Per Lundberg Dec 27 '19 at 09:25
  • For the sake of perspective, Google kept Gmail under a beta tag between 2004 and 2009 which was already a fairly stable, mature product at launch in 2004. Google is pretty reluctant to promote products from Beta status in general. Almost to the point of comedy. – anataliocs May 04 '20 at 22:01
71

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());
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
  • 3
    This still creates an intermediate list, doesn't it? I would like to avoid that. Could the `ImmutableList.Builder` be of any help? – Zoltán Mar 12 '15 at 15:17
  • 4
    @Zoltán You could accumulate the values in the builder directly (see edit) and then call `build()`. – Alexis C. Mar 12 '15 at 15:19
  • 4
    Thank you for this detailed answer. It seems that this is currently being addressed: https://github.com/google/guava/issues/1582, there's also a nice example here (a lot like what you suggested): https://gist.github.com/JakeWharton/9734167 – Zoltán Mar 12 '15 at 15:28
  • 4
    @Zoltán Ah yes; good finds; it simply wraps the second alternative into utility methods. A bit better than defining your own `Collector` class :-) – Alexis C. Mar 12 '15 at 15:30
  • Reference types could be `ImmutableList` (instead of `List`). – palacsint Dec 03 '18 at 09:28
19

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.

Zoltán
  • 21,321
  • 14
  • 93
  • 134
7

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

Grigory Kislin
  • 16,647
  • 10
  • 125
  • 197
  • 1
    This is not _exactly_ true, since `ImmutableCollections.List12` and `ImmutableCollections.ListN` != Guava's `ImmutableList`. From a practical perspective you are mostly correct, but it would still make sense to mention this nuance in your answer. – Per Lundberg Dec 27 '19 at 09:55
4

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).

ColinD
  • 108,630
  • 30
  • 201
  • 202