Isn't this wonderful? Lots of new functionality for free! Er well, no.

Despite following the lambda-dev mailing lists, the problem I now have with Iterable isn't one I foresaw.
Yet it took just a few days of active use of Java SE 8 to realise that Iterable is now a lot
less useful than it was.

The problem as I see it, is that Iterable has been used for two different things in my code.
The first, is as the parameter of a method, where it acted as a very abstract form of collection.
The second is as a "common" interface on a domain object.

Receiving an Iterable parameter

The first case is where a method has been written to receive an Iterable parameter.
In my code, I have defined many methods that take an Iterable, often alongside a varargs array:

void addNames(Name... names);
void addNames(Iterable<Name> names);

This pairing provides great flexibility to callers.
If you have a set of actual name objects, you can use the varargs form.
If you have an array of name objects, you can also use the varargs form.
And if you have virtually any kind of collection, you can use the iterable form.
(That abstract sense of just providing an iteration really broadens the kind of inputs
allowed and blurs the line between collections and non-collections).

Code within the addName(Iterable) method will benefit to some degree
from the new methods on Iterable.
That is because the method receiving the Iterable knows nothing else about the input type other than it is an iterable.

Given this, the addition of the new forEach(Consumer) method is reasonable, as it
provides additional looping options.
But, in reality, there is little difference between these two:

Yes its a new method, but the actual new functionality is limited.
Nevertheless, from this perspective it is a win.

By contrast, the addition of the new spliterator() method simply leads to frustration,
as by itself the method has no value.
What would have had value would have been a stream() method but there are
good reasons why stream() was not added.

Instead, we have to write some rather ugly code to get the actually useful stream:

After much thought and playing with the feel of the options available, I decided to
keep on declaring methods to take Iterable and varargs as that is friendliest to users of the API.
Internally, the mapping has to be done from iterable to stream, which necessitates a convenience method
in a helper for my sanity.

Iterable on domain objects

The second major way I use Iterable is where I had more difficulty.

I have found over the years that some domain objects are little more than a type-safe wrapper
around a collection.
Consider a FooSummary object that contains a list of FooItem.
In many cases, I have made FooSummary implement Iterable<FooItem>:

Doing this allowed the object to be iterated over directly in a foreach loop.

FooSummary summary = ...
for (FooItem item : summary) { ... }

This approach is very useful for those domain objects that are primarily a collection of some
other object, yet are distinct enough to have a class of their own.

However, this kind of approach relied on using Iterable as a kind of "common" interface,
similar to Comparable or Serializable.
These small "common" interfaces work on the basis that they provide a common language across
a very broad spectrum of use cases.
Comparable does nothing other than define the method needed to participate in comparisons.
I used Iterable in exactly that sense - to accept the common language for things that
can be iterated over, without any implication that they were actual collections.

In my opinion, usage of Iterable as a "common" interface has been all but destroyed by Java SE 8.

The FooSummary class is essentially a domain object.
The methods it provides must make sense as a set in their own right.
Adding the iterable() method was perfectly acceptable in exactly the same way as adding
comparable() is. It is a well-known method with clearly defined properties and usage.
However, in Java SE 8, the domain object has had two additional methods foisted upon it.

The issue is that from the perspective of a user of FooSummary,
the two additional methods are net negative, rather than positive.
While there is nothing wrong in theory with being able to call forEach(Consumer)
or spliterator(), it needs to be a specific and separate decision to add them
to the domain object.

In my cases, I definitely do not want those extra methods.
As such, I have no choice but to stop treating Iterable as a "common" interface.
It can now only be used by classes that really make sense to be treated as collections.

In practical terms, this change simply makes life for callers slightly more verbose:

Where the line between a collection and a non-collection could previously be blurred
through Iterable, that option is not really present any more.
As such, I feel an API design tool has been removed from my tool-box.

(Another example I played with was a Tuple interface abstracting over
Pair and Triple. Again, while adding an iterator()
method to Tuple is desirable, adding forEach(Consumer)
and spliterator() was not. Here, none of the available options are very pleasing.)

Summary

The Iterable interface has had two default methods added in Java SE 8.
These make reasonable sense when receiving an iterable.
Unfortunately, the difficulty of obtaining an actual stream is rather annoying.

The more significant problem for me is that Iterable is no longer suitable
for use as a "common" interface.
The extra methods, and the threat of more in future releases mean that the use cases for
Iterable have been reduced.
It should now only be used for classes that really are collections,
something that I find to be a significant reduction in usability.

Any thoughts on iterable, "common" interfaces and default methods?
Has Iterable been ruined by the addition of default methods?

12 comments:

I'm with Oleg, what exactly is the problem with having these default methods defined? It's not as if they enable clients of your object to do anything they couldn't otherwise do. I guess maybe they clutter the interface?

Yes, its about clutter. A FooSummary is not a real collection, its in that blury space where it can sometimes be thought of as a collection. It makes calling code less verbose to be able to call addItems(summary) rather than addItems(summary.getItems()). Its the difference between allowing FooSummary to operate nicely with collections and actually *being* a collection. As its the former, the forEach() and spliterator() methods just don't make sense.

I don't think I feel the same way.If there's little difference between forEach and the for(){...} construct, what's the harm in adding the default method ? As for spliterator, if Iterables are "things that can be iterated over", why wouldn't we want to use them as Streams ? Maybe that's because I still see streams as an improved way of iterating...

Anyway, I fail to see how Iterable now imposes the property of "being a Collection". Stream contents could be generated on-the-fly, just like iterator contents.

Iterable is two things - a super-type of collection and a type that produces an iterator. I didn't realise the distinction would matter until I started using Java 8, where only the former now works.

As an analogy, imagine if default methods has been added to Comparable, say for isGreaterThan()/isLessThan(). Such a decision would have left date classes in a mess, as isBefore()/isAfter() are the correct terminology to use there. The point of the post is that these "common" interfaces exist to provide framework access to things (iteration/comparisons) and that Java 8 no longer has a "common" interface for iteration.

I think I don't share your thoughts about "losing" the iteration behavior with the change introduced by Java 8. I use the Iterable interface as a common role to domain objects, as you do.

The default methods are based only on abstract types and clients are not tightly coupled with any implementation classes, so they can decide to supply any Consumer concrete class or receive an SplitIterator abstract type.

If you have an Iterable type acting as a method argument or as a role on your class, you implicitly have a sequence of objects behind it, so I think clients would be interested in consuming it or splitting it. Moreover, clients could add the behavior they want for consuming the objects in the sequence without managing the iteration explicitly.

Another solution would be to extend the Iterable interface and add the new default methods on it. What do you think about it?

If you are consuming an Iterable within a method then the extra default methods make perfect sense. It is the impact on classes which implement Iterable I'm annoyed by, because many of those are not collection classes, and do not warrant having the two extra methods. Now Java 8 is released, this is a permanent change.

I understand your criticism, but I think you have the same problem when implementing the Iterable interface on a domain class. It exposes the iterator() method, which means for clients that you have an "iteration" sense on your domain class. Clients can invoke the iterator() method as they'd like. In Java, It means you have an implicitly domain class backed by a Collection.

I fail to get your point: as I see it, a Spliterator is just an Iterator for parallel traversal, so if you don't have anything against a client being able to do summary.iterator(), than why do you have something against a client's ability to do summary.spliterator()?And what's not to like about something as readable as:summary.forEach(item -> System.out::println);?