Manipulating a group of objects is something every Java developer does almost every day. In those situations, a lot of developers turn to some concrete collections such as ArrayList or HashSet without second thought. More experienced ones try to avoid accepting concrete classes and use some interface like java.util.List, and even more experienced ones turn to java.util.Collection. But sometimes we don’t need a collection at all, only the ability to iterate over some objects and for those needs, Iterable is beautiful! Let’s see how to improve your programs by using java.lang.Iterable<T>.

Processing collections in constructors or methods

The situation I often see is that a class takes care about a group of objects. Usually it expects clients to provide those objects, either during the construction or afterwards during the “normal” usage of a class. One example might look like this:

 1public class NameFilter {
 2    private final List<String> names;
 3
 4    public NameFilter(List<String> names) {
 5        if (names == null) {
 6            throw new IllegalArgumentException("names is null");
 7        }
 8        this.names = new ArrayList<>();
 9        for (String currentName : names) {
10            if (currentName != null) {
11                this.names.add(currentName);
12            }
13        }
14    }
15}

or if you prefer streams:

 1public class NameFilter {
 2    private final List<String> names;
 3
 4    public NameFilter(List<String> names) {
 5        if (names == null) {
 6            throw new IllegalArgumentException("names is null");
 7        }
 8        this.names = names.stream()
 9                .filter(Objects::nonNull)
10                .collect(Collectors.toCollection(ArrayList::new));
11    }
12}

The code looks pretty reasonable - do some sanity check, filter contents of the parameter based on some criteria, and add all elements that satisfy it into our internal collection (in this case, List<String> names). So what are some problems with this approach?

Don’t force your choices onto callers

First problem is that in this and similar cases we shouldn’t care whether a caller provided a List, a Set, a Queue or something completely different. We only need a way to visit each element once, apply our criteria and add elements into our List, but client should not know about any of that. By using exactly the same type of collection as we use in a class field, we force clients to provide us that same collection type, even though it may mean more work for them.

Also, we inadvertently “tell the secret” - proper encapsulation would mean that our clients don’t care how we store class’ state, they only care about the functionality our class offers them. While in this case it might not look significant, clients might build a mental model and some assumptions about how our code works into their code. For sure we want to avoid that as much as possible.

Why your callers will love Iterable?

I won’t enumerate all known subinterfaces and all known classes that implement Iterable but simply show you a picture of them:

All known subinterfaces and subclasses of Iterable

If you want to see for yourself, here is Iterables API documentation. There are 18 subinterfaces and 58 implementing classes only in JDK 18 and, I’m sure, hundreds of them in various Java libraries. That means by accepting Iterable as a parameter in a constructor or a method of your class, you give enormous flexibility to the caller (client) of your class. A client that wants to work with your API is now free to provide an object of any of those implementing classes and your API will just work!

Let’s see how the code looks like if we accept Iterable:

 1public class NameFilter {
 2    private final List<String> names;
 3
 4    public NameFilter(Iterable<String> names) {
 5        if (names == null) {
 6            throw new IllegalArgumentException("names is null");
 7        }
 8        this.names = new ArrayList<>();
 9        for (String currentName : names) {
10            if (currentName != null) {
11                this.names.add(currentName);
12            }
13        }
14    }
15}

Have you noticed something? Constructor body is completely the same as in the first snippet in which the constructor parameter was List<String>! But with this one, our clients have the most flexibility in how they’ll call us, and that means it’s easiest for them to use our code. I think it’s obvious now that using Iterable costs us nothing but has apparent gains on client side.

As with everything in programming, there is one downside (thank you Christian Semrau for pointing this out!). If you really want to use Streams with Iterables, the code will be slightly more complicated to write due to the fact that you must “wrap” the Iterable. It can look like this:

 1public class NameFilter {
 2    private final List<String> names;
 3
 4    public NameFilter(Iterable<String> names) {
 5        if (names == null) {
 6            throw new IllegalArgumentException("names is null");
 7        }
 8        this.names = StreamSupport.stream(names.spliterator(), false)
 9                .filter(Objects::nonNull)
10                .collect(Collectors.toCollection(ArrayList::new));
11    }
12}

You can even write a function that intelligently returns a Stream based on a class of its parameter and then use that function whenever you want to work with Iterable as if it was a Stream:

 1public static <T> Stream<T> asStream(Iterable<T> iterable) {
 2    if (iterable instanceof Collection<T> collection) {
 3        return collection.stream();
 4    } else {
 5        return StreamSupport.stream(iterable.spliterator(), false);
 6    }
 7}
 8
 9public NameFilter(Iterable<String> names) {
10    if (names == null) {
11        throw new IllegalArgumentException("names is null");
12    }
13    this.names = asStream(names)
14            .filter(Objects::nonNull)
15            .collect(Collectors.toCollection(ArrayList::new));
16}

As a bonus, think about this - every Collection is-a Iterable but not every Iterable is-a Collection! I’ll quote my friend Zoran Horvat here who said it more succinctly than I ever could:

To prove that Iterable ≠ Collection, start from its negation ¬(Iterable ≠ Collection) ⇔ (Iterable = Collection). That is the equivalence relation: reflexive, symmetric and transitive. Symmetry means that Iterable is Collection if and only if Collection is Iterable. Every Collection is an Iterable (public interface Collection<E> extends Iterable<E>). But there exist iterables that are not collections (e.g. SQLWarning). Therefore, the Iterable = Collection predicate does not hold, hence symmetry does not hold, hence equivalence does not hold, and, by reductio ad absurdum we conclude that the original claim must be true: Iterable ≠ Collection Q.E.D.
— Zoran Horvat

Official JLS specification

If you want to dive deeply into specification of enhanced for statement (which is tightly connected to Iterable), you may find it at JLS 14.14.2.

Dear fellow developer, thank you for reading this article about the advantages of accepting Iterable. Until next time, TheJavaGuy saluts you!