Little code, much immutability: Java 8 streams and immutable collections

25 September 2014

I finally got to play with Java 8 streams and lambdas in more than a "Hello, World" way a couple of weeks ago. It was all pretty basic stuff—a map here, a few filters there, a sprinkling of counts—but it's lovely to finally have first-class support for things we've been using in Guava for years, and lambdas make the syntax much more terse and readable.

The only hurdle I had to overcome was making streams work nicely with Guava's immutable collections. I'll not go on about why immutability is a good thing; I've ranted before about how painful mutant mutable objects can be, so if you want to know how it burnt us go have a read.

It turns out that it's straightforward to have the result of processing a stream return an immutable collection, say an ImmutableList. All that's needed is an appropriate implementation of the Collector interface. I started off with one like this:-

public class ImmutableListCollector<T> 
    implements Collector<T, ImmutableList.Builder<T>, List<T>>> {

    @Override
    public Supplier<Builder<T>> supplier() {
        return ImmutableList::builder;
    }

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

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

    @Override
    public Function<Builder<T>, List<T>> finisher() {
        return (builder) -> builder.build();
    }

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

Elsewhere, an ImmutableCollectors class has a static method toImmutableList() to return an instance of one of these, so using this Collector looks like:-

List<String> myList = ImmutableList.of("1", "2");
List<String> filtered = myList.stream()
                              .filter(x -> x.equals("1"))
                              .collect(ImmutableCollectors.toImmutableList());

All well and good, but a little verbose. I was having another read of Collector's Javadoc when I noticed the static method of. Interesting. I wonder what would happen if I used it? This happens:-

public static <T> Collector<T, ImmutableList.Builder<T>, ImmutableList<T>> 
    toImmutableList() {
      return Collector.of(
                ImmutableList.Builder::new, 
                (builder, e) -> builder.add(e),
                (b1, b2) -> b1.addAll(b2.build()), 
                (builder) -> builder.build()
             );

public static <T> Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>>  
    toImmutableSet() {
      return Collector.of(
                ImmutableSet.Builder::new, 
                (builder, e) -> builder.add(e),
                (b1, b2) -> b1.addAll(b2.build()), 
                (builder) -> builder.build()
             );
    }
}

Wow, so little code! With fewer lines than we defined an ImmutableListCollector in, we've got a Collector for both an ImmutableList and an ImmutableSet, with lines to spare for ImmutableMap some day soon!

It's refreshing being able to be more succinct in Java. The next challenge is how we use all of this in existing codebases where Guava functional idioms are already used a fair bit. To preserve our sanity I suspect we'll continue to use Guava where we already do and use Java 8 shininess in new projects, but if you've found a way of having the two live together harmoniously without driving you mad I'd love to hear how.


This originally appeared on our company blog. Picture credit (cropped): Daniel Zedda



blog comments powered by Disqus