… and introducing default methods.
Last time we looked at generators, and more specifically, those generating an infinite sequence. We saw that there were several ways to achieve this:
- The older Java 7 way with an iterator like class
- Using
Stream
’s iterate
method - Using
Stream
’s generate
method
We also saw that when using a Stream
, we had to use the limit
method on our infinite sequence, otherwise it would keep generating and the program couldn’t continue. The problem with using limit was that once we’d got the values, we couldn’t use the stream again to get more. To solve this problem, we used an IntSupplier
and created several streams with it to process batches of values.
What if the sequence we wanted to process was finite. We want to avoid buffering up values in advance. We also want to consume the sequence without knowing up front how many values it will return because of reasons such as:
- We can’t work it out or it’s difficult to
- There is a random element
- We want to decouple the generating function from stream doing the consuming
We saw a simple finite sequence with our Hello World!
example in the first article. In this case, we were not generating the sequence with a function, we were instead processing a pre-initialised list. We also saw we could use an iterator when we discussed infinite sequences, but went on to discuss other ways. We’ll see that in this case, we are virtually forced into the Iterator solution.
Let’s take a simple sequence which we can’t know the length of. We’ll simulate a game where the idea is to keep throwing a die until we get a six. First, we’ll start with a non-functional implementation:
public class SixGame
{
public static class DieThrowIterator implements Iterator<Integer>
{
int dieThrow = 0;
Random rand = new Random(System.nanoTime());
@Override
public boolean hasNext()
{
return dieThrow != 6;
}
@Override
public Integer next()
{
dieThrow = Math.abs(rand.nextInt()) % 6 + 1;
return dieThrow;
}
}
public static void main(String args[])
{
DieThrowIterator dieThrowIterator = new DieThrowIterator();
while (dieThrowIterator.hasNext())
{
System.out.println("You threw a " + dieThrowIterator
.next());
}
}
}
(Note our random number generation is not very robust, but for a simple demonstration, it will do).
Let’s try to use a Stream
. We can’t use Stream
’s iterate
function because it doesn’t allow a stop condition. Also, an IntSupplier
with generate is not an option unless we want to use limit(1)
and create a stream
for each die throw, when we might as well not use a stream
at all.
If we look under the hood at the implementation of IntStream
’s generate
function, we see that it creates an InfiniteSupplyingSpliterator
. This has a problem for us – the tryAdvance
function always returns true
meaning we can never stop.
We could implement our own Spliterator
where our tryAdvance
checks for a stop condition. We can’t simply extend InfiniteSupplyingSpliterator
and override the tryAdvance
method since it’s an inner class of a default access class. So the only way is a cut and paste job rather than inheritance. I’m very nervous about copying large parts of code which might change in future versions; this is saying to me it’s not what was intended. We should look for other ways first.
Let’s look to see how List
does its streaming. List
’s streaming comes from inheriting Iterable
. To create an Iterable
, we only need to implement its iterator()
function – for the example, let’s do so as an inner-class:
public static class DieThrowIterable implements Iterable<Integer>
{
@Override
public Iterator<Integer> iterator()
{
return new DieThrowIterator();
}
}
We can then stream
:
public static void main(String args[])
{
Stream<Integer> stream = StreamSupport.stream(
new DieThrowIterable().spliterator(), false);
stream.map(i -> "You threw a " + i).forEach(System.out::println);
}
Wait a moment… Iterable
is an interface, yet it has a spliterator()
method. How can that be?
If we look at the interface, there is indeed a spliterator()
method creating a new spliterator
for us. If we also look closely, we see the default keyword. This was added in Java 8 (cunningly default is already a reserved word for switch
statements so it won’t break old code). When we write interfaces, we can provide default methods already implemented. Now some people might have reservations about this as it’s turning an interface into effectively an abstract
class. There are some good reasons and advantages this gives us:
- It avoids having to create
abstract
classes to implement interfaces to supply default methods. Thus, there is less boiler-plate to write, and also prevents the consumer of the API implementing the interface when we expected the abstract
class to be used instead. - It helps with multiple inheritance issues (inheriting from two or more classes which C++ supports, but not Java). In Java, the problems with multiple inheritance were avoided by allowing only single inheritance from classes, but we can implement as many interfaces as desired. The problem is we had to implement large portions of those interfaces in each class that used them – a real pain.
- Our API vendors can also now add new methods to interfaces without breaking old code. This is one reason we see a default method here. A new interface would make the JDK even larger and make things more complicated.
- Lambda expressions bind to interfaces with one single method left to implement. If there were more, we couldn’t do this binding. We’ll look at this in a future article.
Note: If we implement more than one interface with an identical default method, and we do not override it by implementing in the class or a parent class, this is an error. Whether eventually it’ll be possible to use Scala-like mix-in traits is an interesting question.
So using Iterable
looks to be the intended way to do finite sequence generators, although the documentation accompanying the default spliterator()
method suggests it should be overridden for better performance. In a later article, we’ll come back and look at spliterator
s in some more detail.
It’s also interesting that generate
and iterate
don’t have stop
conditions which may have been down to performance reasons, forcing us to use Iterable
. Perhaps in a later version, there will be some innovation, as it seems we are pushed back into a hybrid old Java-new Java solution.