Click here to Skip to main content
11,645,889 members (79,089 online)
Click here to Skip to main content

Tagged as

Finite Sequence Generators in Java 8 – Part 2

, 18 Jul 2014 CPOL 3.3K 4
Rate this:
Please Sign up or sign in to vote.
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 [&

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 streams 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 say 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
		public boolean tryAdvance(IntConsumer action)
		{
			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 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, where as 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 spliterators. 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.


License

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

Share

About the Author

TheCannyCoder
United Kingdom United Kingdom
No Biography provided

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150731.1 | Last Updated 19 Jul 2014
Article Copyright 2014 by TheCannyCoder
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid