To begin with this story, let’s first have a look at how to creat a List from Stream in Java
List<String> sublist = list .stream() .filter(...) .collect(Collectors.toList());
This works perfectly fine but what if we want the list to be immutable? We could do this
List<String> immutableSubList = Collections.unmodifiableList(sublist);
or if we would like to use Guava ImmutableList, we could do
ImmutableList<String> immutableSubList = ImmutableList.copyOf(sublist);
However this is a bit awkward to use since the list will be copied one more time. If we want to do this in a lot of places throughout the code base, it is not fluid. Instead, what we want is
ImmutableList<String> sublist = list .stream() .filter(...) .collect(ImmutableCollectors.toList());
This post will discuss how to create the Collector of ImmutableList.
Collector
To create a Collector, we will use the static method of
.
public static<t, A, R> Collector<T, A, R> of( Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A, R> finisher, Characteristics... characteristics);
Here’s a short explaination of the parameters
- The
supplier
returns the resulting object that will be populated by the collector. - The
accumulator
adds an element from the stream into the list created by the supplier. - The
combiner
combines two list instances into one. This function is called by the collector when the stream is in parallel mode. - The
characteristics
provides hints to the Collector how it can optimize reduction implementations. We leave it blank here.
Collect ImmutableList
public static <T> Collector<T, ?, ImmutableList<T>> toList() { return Collector.of( ImmutableList.Builder::new, ImmutableList.Builder::add, (left, right) -> left.addAll(right.build()), (Function<ImmutableList.Builder<T>, ImmutableList<T>>) ImmutableList.Builder::build); }
The implementation uses the Collector.of
method. I use
- ImmutableList.Builder::new to create a supplier.
- ImmutableList.Builder::add to add elements from the stream into the builder.
- The combiner function combines the results of two different supplier instances (created when the stream is in parallel mode). So if we have two builders, we can combine them by calling the
addAll
method, then return the left builder. - ImmutableList.Builder::build to build the ImmutableList.
Collect ImmutableSet
Similarly, we can create immutable set
public static <T> Collector<T, ?, ImmutableSet<T>> toSet() { return Collector.of( ImmutableSet.Builder::new, ImmutableSet.Builder::add, (left, right) -> left.addAll(right.build()), (Function<ImmutableSet.Builder<T>, ImmutableSet<T>>) ImmutableSet.Builder::build, Collector.Characteristics.UNORDERED); }
Here, I use Collector.Characteristics.UNORDERED to indicate that the collection operation does not commit to preserving the encounter order of input elements.
Test cases
@Test public void test_toList() { List<String> list = Lists.newArrayList("a", "b"); ImmutableList<String> sublist = list .stream() .filter(s -> s.charAt(0) == 'a') .collect(ImmutableCollectors.toList()); assertTrue(Iterables.getOnlyElement(sublist).equals("a")); } @Test public void test_toSet() { List<String> list = Lists.newArrayList("a", "b"); ImmutableSet<String> subset = list .stream() .filter(s -> s.charAt(0) == 'a') .collect(ImmutableCollectors.toSet()); assertTrue(Iterables.getOnlyElement(subset).equals("a")); }
Conclusion
The full code can be found at ImmutableCollectors in the library of pengyifan-commons.
<repositories> <repository> <id>oss-sonatype</id> <name>oss-sonatype</name> <url>https://oss.sonatype.org/content/repositories/snapshots/</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> ... <dependency> <groupId>com.pengyifan</groupId> <artifactId>pengyifan-commons</artifactId> <version>0.1.0-SNAPSHOT</version> </dependency>