Search code examples
javajava-stream

How to return false for an empty stream using Stream.allMatch()?


I want to use Stream.allMatch(), but I need a false when the stream is empty.

public static void main (String[] args) throws java.lang.Exception
{
   System.out.println(testMethod(Stream.empty())); // <- expected false (but is true)
   System.out.println(testMethod(Stream.of("match", "match"))); // <- expected true
   System.out.println(testMethod(Stream.of("match", "no match"))); // <- expected false
}
    
private static boolean testMethod(Stream<String> stream) {
   return stream.allMatch(text -> "match".equals(text));
}

https://ideone.com/8Nsjiw

I don't want to use ...

I guess, that I have to use noMatch(), but I didn't get the negation to work correclty.

private static boolean testMethod(Stream<String> stream) {
   // my guess, but the results are wrong
   return !stream.noneMatch(text -> !"match".equals(text));
}

This is not a duplicate of How to return false for an empty list if using Stream.allMatch()? because I use Streams and not Lists.


Maybe X-Y-problem, so here is my upper use-case

It's about status-collecting. Let's simply this as a download-status indicator for the user. (Not my real use-case, but it is close).
I have a table downloads which looks like this

userId download_name download_status data
1 download 1 loading null
2 download 2 ready 0x4545475

I can't change the tables and I'm limited because it is a huge project (sadly not possible to refactor the world).
Anyways, I want to show the user an indicator about his downloads. The indicator should be only visible, if all downloads are ready. But, if there no downloads yet, then the user should not see the indicator.

I have a given repository-method Stream<Downloads> getDownloadsForUser(). The short-circuit is important to reduce the load.


Solution

  • Java's Stream design doesn't make it easy to check in a single pass whether the stream is empty and whether all elements match a given predicate. You can use a local variable to keep track of count (but it must be effectively final, therefore you would have to use a well-known hack such as a 1-element array). This way, you can retain Stream's short-circuiting behaviour while also checking if the stream is empty.

    However, it doesn't make sense in pure mathematics/logic terms to regard an empty stream as not having elements that all match a predicate. This is the vacuous truth concept.

    /**
     * DO NOT USE. This uses the wrong logic: see
     * <a href="https://en.wikipedia.org/wiki/Vacuous_truth#In_computer_programming">vacuous truth</a>.
     */
    private static boolean allMatchExceptReturnFalseIfEmpty(Stream<String> stream) {
        boolean[] empty = { true };
        return stream.allMatch(text -> { empty[0] = false; return "match".equals(text); }) && !empty[0];
    }
    

    The above method treats the empty stream as a special value that returns the opposite result. In many situations, it may be better to take some special handling in such cases, and that can be done if the method throws an exception:

    /**
     * Better. Checks that all elements match, at the same time checking that the stream is not empty.
     */
    private static boolean allMatchNonEmptyStream(Stream<String> stream) {
        boolean[] empty = { true };
        boolean result = stream.allMatch(text -> { empty[0] = false; return "match".equals(text); });
        if (empty[0])
            throw new IllegalArgumentException("Stream must not be empty");
        return result;
    }
    

    Another way of looking at this problem is that you want a three-state result:

    enum MatchResult { EMPTY, ALL_MATCH, SOME_DONT_MATCH }
    
    private static MatchResult allMatchThreeState(Stream<String> stream) {
        boolean[] empty = { true };
        boolean allMatch = stream.allMatch(text -> { empty[0] = false; return "match".equals(text); });
        return (empty[0]) ? MatchResult.EMPTY : allMatch ? MatchResult.ALL_MATCH : MatchResult.SOME_DONT_MATCH;
        
    }
    

    In your case (following the update to your question), you might want to make use of this 3-state value to show, for example, (1) no indicator; (2) "download in progress"; and (3) "download complete". Even if you don't want to have 3 different indicator states right now, having this 3-state method result will give you better flexibility.