1

I'm trying to filter a list based on a condition. Is there an alternative to break in java 8 streams which I can use to stop filtering?

To give an example: suppose I have the following list.

List<String> list = Arrays.asList("Foo","Food" ,"Fine","Far","Bar","Ford","Flower","Fire");

list.stream()
        .filter(str -> str.startsWith("F")) //break when str doesn't start with F
        .collect(Collectors.toList());

I want all strings that begin with "F" from the beginning, as soon as a string is found that does not begin with "F" I want to stop the filtering. Without streams I would do the following:

List<String> result = new ArrayList<>();
for(String s : list){
    if(s.startsWith("F")){
        result.add(s);
    }
    else{
        break;
    }
}

How do I use "break" in streams?

Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
Trillian
  • 411
  • 4
  • 15
  • 3
    Can you use Java 9+? If so, see [`Stream#takeWhile(Predicate)`](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/stream/Stream.html#takeWhile(java.util.function.Predicate)). – Slaw Sep 04 '19 at 15:55
  • @Slaw I didn't know about this function yet. I have probably tried to find a way for an hour. Thanks a lot, takeWhile is exactly what I need. – Trillian Sep 04 '19 at 16:04

2 Answers2

8

You can use the takeWhile operator in Java9 which exactly does what you need. Here's how it looks.

List<String> res = list.stream()
    .takeWhile(s -> s.startsWith("F"))
    .collect(Collectors.toList());
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
  • 1
    I didn't know about this function yet. I have probably tried to find a way for an hour. Thanks a lot, this is exactly what I need. – Trillian Sep 04 '19 at 16:04
3

Here is an option using Java 8:

AtomicBoolean keepAdding = new AtomicBoolean(true);
list.stream()
    .peek(s -> keepAdding.set(keepAdding.get() && s.startsWith("F")))
    .filter(s -> keepAdding.get());

And another possibility as it is evident from the comments that the approach above is not favored:

list.subList(0, list.indexOf(list.stream().filter(s -> !s.startsWith("F"))
    .findFirst()
    .orElse(list.get(list.size() - 1))));
Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
  • 1
    However, this is not guaranteed to behave as expected. – daniu Sep 04 '19 at 16:34
  • @daniu How so? Can you give an example input where it does not behave as expected? – Koray Tugay Sep 04 '19 at 16:36
  • A filter should not be stateful – Felix Sep 04 '19 at 16:38
  • [This answer](https://stackoverflow.com/a/33636377/1746118) hints on the uncertainties while making use of `peek` for operations that rule the iteration. – Naman Sep 04 '19 at 16:38
  • I'll have to summon @Holger to do so, I've seen him butting heads with people (including me) on this topic several times. Edit: aaand I've been ninja'd by Naman with a link to one of Holger's posts :D – daniu Sep 04 '19 at 16:40
  • @Naman Thanks, but I am not using a parallel stream here, and I do not have a short-circuit terminal operation. – Koray Tugay Sep 04 '19 at 16:41
  • Was pointing out the practice in general and not the exact line of code-shared in the answer anyway. – Naman Sep 04 '19 at 16:42
  • The other issue apart from `peek` is that encounter order is not guaranteed for streams (parallel or not) , so the boolean flag is also not strictly correct. – daniu Sep 04 '19 at 16:43
  • @daniu I mentioned in the answer that the first approach is considered not to be guaranteed (although I wish I could see it for myself with some data, in action), and provided a second alternative. – Koray Tugay Sep 04 '19 at 16:50