3

I am new to the Java 8 Stream API and I actually don't understand why my code does not work:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Stream.iterate(0, x -> x+3)
                                        .filter(x -> x>10 && x<100).peek(System.out::println)
                                        .collect(Collectors.toList());
        numbers.forEach(System.out::println);
    }
}

As I understand "laziness" of streams I wrote:

  1. Create stream with numbers divisible by 3

  2. filter it and give me a stream of numbers from range (10, 100)

  3. collect this stream into list

As I can see there is some problem with infinity loop, so peek() prints number from range (12, 99) which is ok, but after that it again prints numbers from (11, 98) etc. Could you explain where I made a mistake?

1 Answer 1

4

Neither the compiler not the runtime know that filter would filter out all numbers beyond 100. Therefore, the runtime continues to generate infinite integers and to apply the filter on them.

You have multiple ways to resolve this :

Use limit to truncate the infinite Stream to a finite Stream. That makes the following filter a bit unnecessary though (only the x>10 test would still be relevant if you set a tight limit).

public static void main(String[] args) {
    List<Integer> numbers = Stream.iterate(0, x -> x+3)
                                    .limit(34)
                                    .filter(x -> x>10 && x<100).peek(System.out::println)
                                    .collect(Collectors.toList());
    numbers.forEach(System.out::println);
}

Use IntStream.range and multiply by 3 :

public static void main(String[] args) {
    List<Integer> numbers = IntStream.range(0, 34)
                                    .map(x -> 3*x)
                                    .collect(Collectors.toList());
    numbers.forEach(System.out::println);
}

In general, the "laziness" of Streams means that they start executing only when they encounter the terminal (final) operation. If the operation requires the processing of all the elements in the list (such as toList does), you shouldn't pass it an infinite Stream.

When you process an infinite Stream, your options are either to truncate it to a finite Stream (using limit) or to have a terminal operation that doesn't have to process all the elements of the Stream (examples : anyMatch, findFirst, findAny).

Sign up to request clarification or add additional context in comments.

7 Comments

Actually I don't understand why the numbers that do not satisfy the condition in filter() are generated. As far as I know, streams in Java are lazy, so there should be generated only numbers that satisfy the condition. Using limit() makes no sense, because I have count how many numbers divided by 3 are between 10 and 100 (or in other range) so using streams makes no sense, because I have to know the collection before it is actually created.
And there is thesame problem with Yours second solution - because when I change a range from 1 to 98765 I don't want to count how many numbers divided by 3 are between them - it would be better to use range(10, 100).filter(x -> x%3 ==0) btw. My question is general about why in my case laziness of Java streams does not work.
@marekx99 You can use a higher limit, lets say 100, in which case you still need the filter (since you know that the filter won't pass numbers beyond 99). The fact that streams are lazy means that they start executing only when they encounter the final operation. If the operation is toList, you are requesting to put in the list all the integers that pass the filter, and in order to know which integers pass the filter, Java has to apply the filter on each integer in the Stream (which is a problem, since there's an infinite number of them).
@marekx99 I hope that my comment clarified why it doesn't work this way. When you process an infinite Stream, your options are either to truncate it to a finite Stream (using limit) or to have a terminal operation that doesn't have to process all the elements of the Stream (examples : anyMatch, findFirst, findAny).
@marekx99: and your second oversight which has not been mentioned yet: the int value range is finite, even if your stream is not. The int values will overflow when you don’t limit the stream. So you might use IntStream.range(0, Integer.MAX_VALUE) to solve all your problems (it doesn’t have to be a big performance problem if the hotspot optimizer is smart enough)…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.