Search code examples
javajava-streamillegalstateexception

Why does Java compiler allow to reuse a Stream?


Let's imagine the following code:

Stream<Integer> numberStream = ...;

Predicate<Integer> isEven = ...;
Predicate<Integer> isOdd = ...;

List<Integer> evenNumbers = numberStream
    .filter(isEven)
    .collect(Collectors.toList());

List<Integer> oddNumbers = numberStream 
    .filter(isOdd)
    .collect(Collectors.toList()); // this line will throw IllegalStateException

The above code compiles without any warnings. However, trying to run it will always result in an IllegalStateException.

After looking into it, I found out that one Stream can only have one terminal operation, so basically there is no point in storing it inside a variable.

It seems to me that this would be a very easy error to spot by the compiler. Why does it compile without errors? Is there some use case where code like that would be useful?


Solution

  • The compiler is simple, in a sense.

    It verifies that your code is legal according to the rules of the Java language and that all your calls match what the Java language rules and type system requires.

    Neither the language rule nor the type system somehow "encodes" that a Stream can not be reused. It's simply a fact that the compiler doesn't know about.

    Think of streams as a domain-specific language built on top of Java. The compiler only knows the lower "Java" layer of that concept, but doesn't understand the rules of the higher-level Streams "language".

    So while the compiler could conceivably be told about the rules of that specific language, this is a dangerous road to go, because there are many, many domain specific languages like that which one could conceivably want to verify and doing them all right is ... unlikely.