Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Finite Sequence Generators in Java 8

5.00/5 (1 vote)
12 Jul 2014CPOL5 min read 7.9K  
Finite sequence generators in Java 8

… 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:

Java
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:

Java
public static class DieThrowIterable implements Iterable<Integer>
{
    @Override
    public Iterator<Integer> iterator()
    {
        return new DieThrowIterator();
    }
}

We can then stream:

Java
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 spliterators 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)