GitHub LinkedIn Feed

Fluent Operations on Collections in Java

Aug 30, 2015
Table of Contents

As far as language features go, collections are a basic necessity in Java. You can add, remove and retrieve elements, but it has always frustrated me that more complex operations, like filtering or mapping, require lines of grotesque boilerplate code with iterators. A library that you may already be using can help. It’s called Guava. Before I go any further, I should mention that none of these problems exist in Java 8 with its shiny streams. Hence, this guide is only for the developers stuck in the yesteryear, because of either Android or some unupdatable legacy system.

Fluent iterable

First we need to introduce the notion of fluent iterables. These are essentially a simplified relative of streams found in many functional languages. They start with an initial dataset, operations are performed on them and for each one a new instance is returned. In the end, you either use the resulting iterable or convert it into another type.

Let’s consider an example with many different operations and go through them one by one.

FluentIterable
    .from(database.getClientList())
    .filter(activeInLastMonth())
    .transform(Functions.toStringFunction())
    .limit(10)
    .skip(5)
    .toList();

Creation

Before you start performing any operations, you need to create a fluent iterable with some initial data. You can do this by feeding the source collection into FluentIterable.from(). Subsequent sources can be added with append() — these elements will be added to the end.

Filter

Filtering out elements is done by calling filter() with a predicate implementation. This is essentially a substitute for a function that returns true for elements to keep (not the elements to filter out, as it may seem at first) and false otherwise.

For those familiar with functional programming, “transform” is the same as “map”: it creates a new collection by applying a function to each element. Remember that the function can return the same type it received or a completely different one, therefore changing the parameterization of the output collection.

Limit

Limiting the iterable by length drops any elements past that length (counting from the start).

Skip

Skipping is the same as limiting, except elements will get dropped from the start.

Joiner

So far we only discussed operations that produce other collections, but what if your end result needs to be a string? This is where joiners come in.

To create a joiner, call Joiner.on() with the desired separator (Guava allows a character or a string). The instance can then be passed to the join() method on the fluent iterable to construct a string.

Note that you can also use the join() method on the joiner itself if you have a regular iterable.

Realistic example

Looking at the primitive examples in the documentation does little to demonstrate the value provided by the fluent iterables, so let’s look at a semi-realistic example instead.

Problem

Given a data packet, expressed as a collection of bytes, extract the first 10 bytes from the payload (excluding the header) and print them as ASCII characters. You may have seen such representation in packet sniffing software.

Solution

Breaking down the problem into stages:

  • Use the byte array as a data source
  • Trim the header
  • Take the first 10 bytes
  • Convert each byte into a character
  • Flatten the array into a string

Each stage can now be translated into an operation on the fluent iterable.

FluentIterable
    .from(bytes)
    .skip(HEADER_BYTES)
    .limit(10)
    .transform(new Function<Byte, Character>() {
        @Override
        public Character apply(Byte b) {
            return (char) b.byteValue();
        }
    })
    .join(Joiner.on('-'));

Types

Eagle-eyed readers might have noticed that all examples are kept in their generic iterable form. This is a common trick in Guava to keep the collections portable while you process them. Once you are finished, the iterable can be converted into the desired type, such as an array list.

ArrayList<T> arrayList = Lists.newArrayList(iterable);

Conclusion

Guava can be an extremely useful library, but some developers incorrectly assume that it’s only useful for common collections (understandable, considering that it used to be called “Google Collections”). Nowadays it has grown to include a plethora of utilities to make your day-to-day coding easier, such as the fluent iterables discussed here. With this in mind, I recommend taking a good look at Guava before the next time you’re forced into writing tedious boilerplate code.

  • java
  • functional-programming