In the last few weeks I've finally had the chance to use lambdas and streams in Java SE 8 in anger. In doing so, I've found much to like, but some rough edges.
Iterable woes
The Iterable
interface was added in Java SE 5 to enable the foreach loop to work.
It was defined with a single method that returns an iterator:
public interface Iterable<T> { Iterator<T> iterator(); }
The simplicity of the interface was based around the primary use case, the foreach loop, which needed nothing more than a source of an iterator.
In Java SE 8, the interface changed, as it added two default methods:
public interface Iterable<T> { Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } }
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:
void addNames(Iterable<Name> names) { for (Name name : names) { ... } } void addNames(Iterable<Name> names) { names.forEach(() -> ...); }
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:
void addNames(Iterable<Name> names) { StreamSupport.stream(names.spliterator(), false) .doMyWizzyStreamThing(); }
As an aside, arrays manage this better:
void addNames(Name[] names) { Stream.of(names) .doMyWizzyStreamThing(); }
Now why oh why oh why is there no Stream.of(Iterable)
method?
Actually, I know why, but that is a story for another day...
Streamable?
Perhaps you're thinking that the reason why Iterable
isn't supported well is that
there is a Streamable
interface that you are supposed to use for this purpose instead?
Er, no. There was a Streamable
interface, but it got removed during development.
There is no JDK supplied abstraction for a type that provides streams.
Perhaps this is philosophical - you could just declare and call using a supplier:
void addNames(Supplier<Stream<T>> streamSupplier) { streamSupplier.get() .doMyWizzyStreamThing(); } Collection<T> coll = ... addNames(coll::stream)); T[] array = ... addNames(Stream.of(array));
Definitely a different kind of use of the type system.
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>
:
public class FooSummary implements Iterable<FooItem> { List<FooItem> items; @Override public Iterator<FooItem> iterator() { return items.iterator(); }
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:
FooSummary summary = ... for (FooItem item : summary.getItems()) { ... }
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?