12,689,589 members (25,546 online)
Technical Blog
alternative version

4.1K views
4 bookmarked
Posted

# Finite Sequence Generators in Java 8 – Part 2

, 18 Jul 2014 CPOL
 Rate this:
Finite Sequence Generators in Java 8 – Part 2

In the last couple of articles, we looked at generators. First, we looked at ways of generating an infinite sequence. In the second, we saw a way of generating a finite sequence. Let’s look at a few more aspects before we move on.

In the finite sequence article, we saw that unless we wanted to limit ourselves to a certain number of values, we couldn’t use generate and iterate in a simple manner. This was because there was no way of indicating a stop condition. Limit is fine if we know how many values we need, but not if we don’t. If we use limit, we’d have to create a new stream to get further values. There are a couple of other methods we could use for generating finite sequences without having to resort to using Iterable.

Let’s go back to our die throwing `SixGame` example from the last article. Instead of using an Iterator/Iterable, we’ll use an `IntSupplier` coupled with `IntStream`’s generate method. If any of that is new to you, then first review the article on generators with infinite sequences. We’re going to attempt (and I’m not saying this is good practice) to stop generating when we get a `Six` by throwing an exception:

```public class SixGame
{
public static class DieThrowSupplier implements IntSupplier
{
private Random rand = new Random(System.nanoTime());
private boolean done = false;

@Override
public int getAsInt()
{
if (!done)
{
int dieThrow = Math.abs(rand.nextInt()) % 6 + 1;

if (dieThrow == 6)
{
done = true;
}

return dieThrow;
}
else
{
throw new NoSuchElementException();
}
}
}

public static void main(String args[])
{
DieThrowSupplier dieThrows = new DieThrowSupplier();

IntStream myStream = IntStream.generate(dieThrows);

try
{
myStream.mapToObj(i -> "You threw a " + i).forEach(
System.out::println);
}
catch (NoSuchElementException e)
{
// Escaped
}
}
}```

Something here that’s new is the `mapToObj` call. We’re starting out with an `IntStream`, but we want to create a message which is a `String`. Thus we need to change the ‘shape’ of the `stream` from `Integer` to `Object` (there is no special `String stream`) and we can do that with `mapToObj`. It works like map, but instead of expecting an `Integer` being returned from the function, it expects an `Object`.

We have to catch the exception, but luckily (or perhaps sloppily given this is a demonstration) we are using a side-effect to do something with the `string` we generate: printing in `forEach`. Once we go parallel though, we need to remove side effects. Although we’ve not covered it yet, what we need to do is collect the results from the `stream`, perhaps in a list, and then perform the printing outside of the `stream` chain. Although this seems a lot for our simple game, getting `stream`s to work properly in parallel is one of the more difficult tasks that we’re going to have to master eventually.

Our problem in the parallel world is going to be that we’re collecting, but we need to assign that collection to something when the `stream` is done. Try changing the `try`/`catch` code to the broken:

```List<String> l = null;

try
{
l = myStream.parallel().mapToObj(i -> "You threw a " + i)
.collect(Collectors.toList());
}
catch (NoSuchElementException e)
{
// Escaped
}

l.stream().forEach(System.out::println);
```

Nothing gets printed this time, and we crash with a `NullPointerException`. Given we throw an exception during the `stream` which we catch after the assignment and not as part of the `stream`, the assignment never happens. Thus the list, l, stays `null`. We went through all the motions and got nothing for our troubles. Perhaps we could try making special collectors to handle exceptions, but given an exception is almost certainly a side-effect, we should avoid these when going parallel. I can also imagine that catching exception outside of a `stream` and hoping we still get all the results might be quite flaky as we’re relying on the implementation to make it work. Implementations change, and other implementations come along. My verdict is – unless Oracle says otherwise, is avoid.

We also discussed that we wanted to avoid implementing a whole spliterator if there was another way available. To recap, a spliterator is an iterator that can be split into batches of work and is what drives streams. Getting that right isn’t trivial. We saw that we couldn’t get access to override `InfiniteSupplyingSpliterator` in order to make a version we could terminate. However, there exists a spliterator that is just missing `tryAdvance` which we use to inject the next value into the `stream` and indicate when we’re done. This is `AbstractSpliterator`, in particular `AbstractIntSpliterator`, which we can extend. Let’s have a look at our game using one of those:

```public class SixGame
{
public static class DieThrowSpliterator extends
Spliterators.AbstractIntSpliterator
{
private Random rand = new Random(System.nanoTime());
private boolean done = false;

protected DieThrowSpliterator()
{
super(Long.MAX_VALUE, 0);
}

private int rollDie()
{
int dieThrow = Math.abs(rand.nextInt()) % 6 + 1;

if (dieThrow == 6)
{
done = true;
}

return dieThrow;
}

@Override
{
if (action == null)
{
throw new NullPointerException();
}

if (done)
{
return false;
}

action.accept(rollDie());

return true;
}

@Override
public boolean tryAdvance(Consumer<? super Integer> action)
{
if (action == null)
{
throw new NullPointerException();
}

if (done)
{
return false;
}

action.accept(rollDie());

return true;
}
}

public static void main(String args[])
{
Stream<Integer> stream = StreamSupport.stream
(new DieThrowSpliterator(), false);

stream.map(i -> "You threw a " + i)
.forEach(System.out::println);
}
}```

First notice that we are creating the `stream` in the same way we did when using an `Iterable`, but instead we are creating a `spliterator` which we pass to the `stream`. The second thing to notice is that we have to implement two `tryAdvance` functions. These take `Consumers` which will use our value. The first is a `true IntConsumer`, whereas the second is a `Consumer` of any type which can hold an `Integer` (`Object`, `Number` and `Integer`). I’ve kept the `null` check used in other `spliterator`s. If we’re already done, we can return `false`, otherwise pass a roll to the action and return `true`. The parent constructor of our `spliterator` takes two values, the first being how many values we expect (we don’t know) and flags for characteristics of the `spliterator` (`0` being none of them).

No doubt we could continue the discussion on generation, particularly as we now have several ways to solve problems. For now, we’ll move on and look at a few more aspects of Java 8 functional programming and lambda expressions.

## Share

 United Kingdom
No Biography provided