javajava-stream

How to simplify a complex filter of an optionals stream


I'm new to Optional in Java and would like to implement the following logic.

Input is a list of strings (it's not empty). If the list contains chocolate, I want this item. Otherwise, I want the first item matching the pattern .*ap.*. If I still don't get an item, I want the first item of the list.

I already set up this piece of code which does the job but I want to know whether this code can be simplified:

List<String> testList = Arrays.asList("banana", "apple", "cheese", "chocolate");

String food = testList.stream()
                      .filter(str -> str.equals("chocolate"))
                      .findFirst()
                      .orElseGet(() -> testList.stream()
                                               .filter(str -> str.matches(".*ap.*"))
                                               .findFirst()
                                               .orElseGet(() -> testList.get(0)));

Solution

  • Streams are not always the right tool for the job. Or for the whole job. Everything does not need to go into a single statement. But if you insist on a single-statement solution then you can get one that is relatively clean and not necessarily too wasteful by concatenating filtered streams:

        String food = Stream.concat(
                testList.stream().filter(s -> s.equals("chocolate"))
                testList.stream().filter(s -> s.matches(".*ap.*"))
            ).findFirst()
            .orElse(testList.get(0));
    

    The concat() produces a stream that lazily concatenates the other two, so in combination with findFirst(), that avoids reading any elements from the second filtration if the first yields any elements. The resulting Optional is empty if and only if no element of the list passes either filter, and in this case, the first element of the list is selected (or an IndexOutOfBoundsException is thrown if you were wrong about the list being non-empty).

    But I would probably write this, which I find easier to follow:

        String food;
    
        if (testList.contains("chocolate")) {
            food = "chocolate";
        } else {
            food = testList.stream()
                    .filter(s -> s.matches(".*ap.*"))
                    .findFirst()
                    .orElse(testList.get(0));
        }