Can Java 8 Streams operate on an item in a collection, and then remove it?

JavaCollectionsJava 8FilteringJava Stream

Java Problem Overview


Like just about everyone, I'm still learning the intricacies (and loving them) of the new Java 8 Streams API. I have a question concerning usage of streams. I'll provide a simplified example.

Java Streams allows us to take a Collection, and use the stream() method on it to receive a stream of all of its elements. Within it, there are a number of useful methods, such as filter(), map(), and forEach(), which allow us to use lambda operations on the contents.

I have code that looks something like this (simplified):

set.stream().filter(item -> item.qualify())
    .map(item -> (Qualifier)item).forEach(item -> item.operate());
set.removeIf(item -> item.qualify());

The idea is to get a mapping of all items in the set, which match a certain qualifier, and then operate through them. After the operation, they serve no further purpose, and should be removed from the original set. The code works well, but I can't shake the feeling that there's an operation in Stream that could do this for me, in a single line.

If it's in the Javadocs, I may be overlooking it.

Does anyone more familiar with the API see something like that?

Java Solutions


Solution 1 - Java

You can do it like this:

set.removeIf(item -> {
    if (!item.qualify())
        return false;
    item.operate();
    return true;
});

If item.operate() always returns true you can do it very succinctly.

set.removeIf(item -> item.qualify() && item.operate());

However, I don't like these approaches as it is not immediately clear what is going on. Personally, I would continue to use a for loop and an Iterator for this.

for (Iterator<Item> i = set.iterator(); i.hasNext();) {
    Item item = i.next();
    if (item.qualify()) {
        item.operate();
        i.remove();
    }
}

Solution 2 - Java

In one line no, but maybe you could make use of the partitioningBy collector:

Map<Boolean, Set<Item>> map = 
    set.stream()
       .collect(partitioningBy(Item::qualify, toSet()));

map.get(true).forEach(i -> ((Qualifier)i).operate());
set = map.get(false);

It might be more efficient as it avoids iterating the set two times, one for filtering the stream and then one for removing corresponding elements.

Otherwise I think your approach is relatively fine.

Solution 3 - Java

There are many approaches. If you use myList.remove(element) you must override equals(). What I prefer is:

allList.removeIf(item -> item.getId().equals(elementToDelete.getId()));

Good luck and happy coding :)

Solution 4 - Java

> After the operation, they serve no further purpose, and should be removed from the original set. The code works well, but I can't shake the feeling that there's an operation in Stream that could do this for me, in a single line.

You cannot remove elements from the source of the stream with the stream. From the Javadoc:

>Most stream operations accept parameters that describe user-specified behavior..... To preserve correct behavior, these behavioral parameters: > > - must be non-interfering (they do not modify the stream source); and > - in most cases must be stateless (their result should not depend on any state that might change during execution of the stream pipeline).

Solution 5 - Java

What you really want to do is to partition your set. Unfortunately in Java 8 partitioning is only possible via the terminal "collect" method. You end up with something like this:

// test data set
Set<Integer> set = ImmutableSet.of(1, 2, 3, 4, 5);
// predicate separating even and odd numbers
Predicate<Integer> evenNumber = n -> n % 2 == 0;

// initial set partitioned by the predicate
Map<Boolean, List<Integer>> partitioned = set.stream().collect(Collectors.partitioningBy(evenNumber));

// print even numbers
partitioned.get(true).forEach(System.out::println);
// do something else with the rest of the set (odd numbers)
doSomethingElse(partitioned.get(false))

Updated:

Scala version of the code above

val set = Set(1, 2, 3, 4, 5)
val partitioned = set.partition(_ % 2 == 0)
partitioned._1.foreach(println)
doSomethingElse(partitioned._2)`

Solution 6 - Java

Nope, your implementation is probably the simplest one. You might do something deeply evil by modifying state in the removeIf predicate, but please don't. On the other hand, it might be reasonable to actually switch to an iterator-based imperative implementation, which might actually be more appropriate and efficient for this use case.

Solution 7 - Java

if I understand your question correctly:

set = set.stream().filter(item -> {
    if (item.qualify()) {
        ((Qualifier) item).operate();
        return false;
    }
    return true;
}).collect(Collectors.toSet());

Solution 8 - Java

I see Paul's clarity concern when using streams, stated in the top answer. Perhaps adding explaining variable clarifies intentions a little bit.

set.removeIf(item -> {
  boolean removeItem=item.qualify();
  if (removeItem){
    item.operate();
  }
  return removeItem;
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichael MachaView Question on Stackoverflow
Solution 1 - JavaPaul BoddingtonView Answer on Stackoverflow
Solution 2 - JavaAlexis C.View Answer on Stackoverflow
Solution 3 - Javapanayot_kulchev_bgView Answer on Stackoverflow
Solution 4 - Javauser7502825View Answer on Stackoverflow
Solution 5 - JavaDmitriy YefremovView Answer on Stackoverflow
Solution 6 - JavaLouis WassermanView Answer on Stackoverflow
Solution 7 - Javauser_3380739View Answer on Stackoverflow
Solution 8 - Javavacant78View Answer on Stackoverflow