22
\$\begingroup\$

This seems like an easy thing to mess up, so I'd be grateful if you could think of any edge-cases that I've missed.

public class StreamIterable<T> implements Iterable<T> {

  private final Stream<T> stream;

  public StreamIterable(Stream<T> stream) {
    this.stream = stream;
  }

  @Override
  public Iterator<T> iterator() {
    return new StreamIterator<>(stream);
  }
}

where

public class StreamIterator<T> implements Iterator<T> {
  private final Spliterator<T> spliterator;

  private boolean nextIsKnown = false;
  private T next = null;

  public StreamIterator(Stream<T> stream) {
    this.spliterator = stream.spliterator();
  }

  @Override
  public boolean hasNext() {
    if (nextIsKnown)
        return true;
    return spliterator.tryAdvance(t -> {next = t; nextIsKnown = true;});
  }

  @Override
  public T next() {
    if (nextIsKnown) {
        return resetAndReturnNext();
    }
    if (!hasNext())
        throw new NoSuchElementException();
    return resetAndReturnNext();
  }

  private T resetAndReturnNext() {
    T result = next;
    nextIsKnown = false;
    next = null;
    return result;
  }
}

Tests

public class StreamIteratableTest {

    @Test
    public void empty() {
        assertThat(new StreamIterable<>(Stream.<String>empty()), Matchers.emptyIterable());
    }


    @Test
    public void matcher() {
        Stream<String> stream = Stream.of("1", "2", "3");
        assertThat(new StreamIterable<>(stream), Matchers.contains("1", "2", "3"));
    }

    @Test
    public void torture_empty() {
        StreamIterator<Object> empty = new StreamIterator<>(Stream.empty());

        assertFalse(empty.hasNext());
        try {
            empty.next();
            fail();
        } catch (NoSuchElementException expected) {
        }

        assertFalse(empty.hasNext());
        assertFalse(empty.hasNext());
        try {
            empty.next();
            fail();
        } catch (NoSuchElementException expected) {
        }
        try {
            empty.next();
            fail();
        } catch (NoSuchElementException expected) {
        }
    }

    @Test
    public void dont_call_hasNext() {
        StreamIterator<Object> twoItems = new StreamIterator<>(Stream.of("1", "2"));

        assertEquals("1", twoItems.next());
        assertEquals("2", twoItems.next());
        try {
            twoItems.next();
            fail();
        } catch (NoSuchElementException expected) {
        }
    }

    @Test
    public void repeat_call_hasNext() {
        StreamIterator<Object> twoItems = new StreamIterator<>(Stream.of("1", "2"));

        assertTrue(twoItems.hasNext());
        assertTrue(twoItems.hasNext());
        assertEquals("1", twoItems.next());
        assertTrue(twoItems.hasNext());
        assertEquals("2", twoItems.next());
        assertFalse(twoItems.hasNext());
        assertFalse(twoItems.hasNext());
    }   
}
\$\endgroup\$
26
\$\begingroup\$

Two things:

  1. I think you have missed the native implementation. Are you intentionally re-inventing the wheel? Streams have an iterator() method.

  2. Note that streams cannot be reversed, so, while you can create an iterator once, from the stream, you cannot create a second iterator.... In other words, you cannot loop more than once through your iterable.

Consider reworking your class as:

public class StreamIterable<T> implements Iterable<T> {

  private final Stream<T> stream;

  public StreamIterable(Stream<T> stream) {
    this.stream = stream;
  }

  @Override
  public Iterator<T> iterator() {
    return stream.iterator();
  }
}

That's it, no additional code needed. You can still only iterate it once, but, you can add the above to an enhanced-for loop:

for (String v : new StreamIterable(Files.lines(...)) {
    ....
}
\$\endgroup\$
  • 2
    \$\begingroup\$ Oh good grief - I evidently looked for Iterable but not Iterator. Thanks for that, another carefully crafted class bites the dust :-) \$\endgroup\$ – Duncan McGregor Nov 21 '14 at 0:00
  • \$\begingroup\$ @rolfl Your code won't even compile. stream.iterator() returns Iterator<? extends T>. UPD: Ouch, pardon me, I was talking about Stream API for Android: github.com/aNNiMON/Lightweight-Stream-API. \$\endgroup\$ – ddmytrenko Oct 13 '16 at 9:31
16
\$\begingroup\$

You should not need a special class for this. Method references can be cast to functional interfaces. In this particular case, however, remember that the stream returned by Files::lines needs to be closed to prevent leaks:

try (Stream s = Files.lines(...)) {
  for (String v : (Iterable<String>) s::iterator) {
      ...
  }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ There is more detail on this trick here and here. \$\endgroup\$ – Vadzim Jul 29 '15 at 14:49
3
\$\begingroup\$

One should note that a Stream can never really be an Iterable because it can't be invoked more than once. However, if you are certain that the Stream will only be iterated once, to create an Iterable from a stream, one only need to use the expression myStream::iterator

\$\endgroup\$
2
\$\begingroup\$

If you want to have a more proper Iterable, consider generating the Stream using a closure. It's syntactically displeasing, but the paradigm allows you to iterate more than once:

public class StreamIterable<T> implements Iterable<T> {

    private final Supplier<Stream<T>> streamGenerator;

    public StreamIterable(Supplier<Stream<T>> streamGenerator) {
        this.streamGenerator = streamGenerator;
    }

    @Override
    public Iterator<T> iterator() {
        return streamGenerator.get().iterator();
    }
}

You can see the difference by running this snippet:

{
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(3, 98, 4);
        Iterable<Integer> infinitelyIterable = new StreamIterable<>(() -> list.stream().map(i -> i + 10));
        Iterable<Integer> onceIterable = list.stream()::iterator;
        for (int i : infinitelyIterable) {
            System.out.println(i);
        }
        for (int i : infinitelyIterable) {
            System.out.println(i);
        }
        for (int i : onceIterable) {
            System.out.println(i);
        }
        for (int i : onceIterable) {
            System.out.println(i);
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

As an Iterable is a functional interface with a method just returning the iterator, you could also write:

Iterable<T> it = () -> stream.iterator();
\$\endgroup\$
  • 3
    \$\begingroup\$ Welcome to Code Review! At the moment your answer is only a stub. Please provide a reasoning for why this is better than what the OP or other answers have provided. Also keep in mind, that the question is already more than 4 years old. \$\endgroup\$ – AlexV Jun 12 at 11:28
  • \$\begingroup\$ @AlexV I disagree, I consider this an "Oh, duh! Why didn't I think of that?" answer and a really good one. This is how I would write it. \$\endgroup\$ – Simon Forsberg Jun 12 at 14:40
  • 1
    \$\begingroup\$ But just for the sake of it I have edited this answer and improved it to make it even better (basically just explaining a bit what it's doing). \$\endgroup\$ – Simon Forsberg Jun 12 at 14:41
  • \$\begingroup\$ @SimonForsberg: My Java knowledge is a little bit antiquated - to put it mildly - so that "Oh, duh!" effect was beyond me. I like the answer much more with your added explanation. \$\endgroup\$ – AlexV Jun 12 at 14:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.