Mayday
Mayday

Reputation: 5136

Operations between java Streams

I am trying to find the fastest way, and less complex way to do some union/exclude/intersection operations over Java SE 8 Streams.

I am doing this:

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");
Stream<String> fruitStream2 = Stream.of("orange", "kiwi", "melon", "apple", "watermelon");

//Trying to create exclude operation
fruitStream.filter(
    item -> !fruitStream2.anyMatch(item2 -> item2.equals(item)))
.forEach(System.out::println);
// Expected result: fruitStream - fruitStream2: ["banana","pear"]

I get the following Exception:

java.lang.IllegalStateException: stream has already been operated upon or closed

If I was able to do this operation, I could develope myself all the rest, union, intersection etc...

So, the 2 points are:

1) What am I doing wrong in this solution to get that Exception?

2) Is there a less complex way to perform operations between 2 streams?

Note

I want to use streams to learn about them. Dont want to transform them into arrays or lists

Upvotes: 4

Views: 183

Answers (4)

shmosel
shmosel

Reputation: 50716

This is kind of silly and it won't give you great performance, but it does fulfill your requirement of not (explicitly) creating arrays or lists:

public static void main(String[] args) {
    new Object() {
        Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");
        Stream<String> fruitStream2 = Stream.of("orange", "kiwi", "melon", "apple", "watermelon");
        {
            fruitStream2.forEach(f -> fruitStream = fruitStream.filter(Predicate.isEqual(f).negate()));
            fruitStream.forEach(System.out::println);
        }
    };
}

Output:

banana
pear

EDIT A (slightly) more straightforward approach:

public static void main(String[] args) {
    Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");
    Stream.of("orange", "kiwi", "melon", "apple", "watermelon")
            .map(Predicate::isEqual)
            .reduce(Predicate::or)
            .map(Predicate::negate)
            .map(fruitStream::filter)
            .orElse(fruitStream)
            .forEach(System.out::println);
}

Upvotes: 0

bradimus
bradimus

Reputation: 2523

The first time item -> !fruitStream2.anyMatch(item2 -> item2.equals(item)) executes, it consumes fruitStream2. fruitStream2 can not be used again to filter the second item in fruitStream1.

Rather than using a second stream, why not create a Set and use contains?

Set<String> otherFruits = new HashSet<>(); // Add the fruits.
fruitStream.filter(f -> !otherFruits.contains(f)).forEach(System.out::println);

Upvotes: 6

Joop Eggen
Joop Eggen

Reputation: 109547

A Stream is an inside iteration/processing executed only once. That is a very limited area.

Supplier<Stream<String>> fruitStream2S = () ->
        Stream.of("orange", "kiwi", "melon", "apple", "watermelon");

fruitStream.filter(item -> !fruitStream2s.get().anyMatch(item2 -> item2.equals(item)))
    .forEach(System.out::println);

Which is not efficient.

Upvotes: 1

Nicolas Filotto
Nicolas Filotto

Reputation: 44965

What am I doing wrong in this solution to get that Exception?

You cannot consume several times the same Stream and here you consume fruitStream2 as many times as you have elements in fruitStream

Is there a less complex way to perform operations between 2 streams?

You could convert the second Stream as a Set:

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");
Set<String> fruitSet = Stream.of("orange", "kiwi", "melon", "apple", "watermelon")
    .collect(Collectors.toSet());

fruitStream.filter(item -> !fruitSet.contains(item)).forEach(System.out::println);

Output:

banana
pear

Upvotes: 4

Related Questions