Потоки Java: используйте anyMatch() vs count() › 0 для проверки хотя бы одного вхождения

Обзор

При работе с потоками в Java бывают случаи, когда мы просто хотим определить, есть ли хотя бы одно вхождение, которое соответствует нашему предикату или критериям.

В этой статье мы обсудим, почему использование метода anyMatch(predicate) в Java Stream API безопаснее и эффективнее, чем использование filter(predicate).count > 0 в этих ситуациях.

Потоки добавлены в Java 8 (JavaDoc)

Короткое замыкание

Фундаментальное различие между anyMatch() и count() заключается в том, что anyMatch() является короткозамкнутым, что означает, что он остановится на первом совпадении, тогда как count() подсчитает все совпадения перед возвратом.

В зависимости от размера потока эта разница может иметь огромное влияние на производительность.

Поток из 100 000 объектов

Представьте, что вы работаете со списком из 100 000 объектов Employee, где мы хотим определить, неактивны ли какие-либо сотрудники:

Пример кода

package com.codebyamir.streams;
import java.util.ArrayList;
import java.util.List;
class Employee {
    private boolean isActive;
    public Employee(boolean isActive) {
        this.isActive = isActive;
    }
    public boolean isActive() {
        return isActive;
    }
    public void setActive(boolean active) {
        isActive = active;
    }
}
public class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        // Create 100,000 objects
        for (int i = 0; i < 100_000; i++) {
            boolean active = false;
            if (i % 4 == 0) {
                active = true;
            }
            employees.add(new Employee(active));
        }
        boolean result = false;
        System.out.println("count() > 0");
        System.out.println("---------------------");
        System.out.println("Start time:\t" + System.currentTimeMillis());
        result = employees.stream().filter(e -> !e.isActive()).count() > 0;
        System.out.println("End time:\t" + System.currentTimeMillis());
        System.out.println();
        System.out.println("anyMatch()");
        System.out.println("---------------------");
        System.out.println("Start time:\t" + System.currentTimeMillis());
        result = employees.stream().anyMatch(e -> !e.isActive());
        System.out.println("End time:\t" + System.currentTimeMillis());
    }
}

Пример вывода

count() > 0
---------------------
Start time:	1583177821492
End time:	1583177821512
anyMatch()
---------------------
Start time:	1583177821512
End time:	1583177821513

Как видно из выходных данных примера, anyMatch значительно быстрее (1 мс), чем count (20 мс).

Бесконечный поток

Если мы в конечном итоге создадим бесконечный поток элементов, как в приведенном ниже коде, то метод count() никогда не завершится, в то время как подход anyMatch() завершится при обнаружении первого совпадения.

.filter(predicate).count() › 0

IntStream infiniteStream = IntStream.iterate(0, i -> i + 2);
// Never terminates
boolean test = stream.filter(i -> i <= 50).count() > 0;

.anyMatch(предикат)

IntStream infiniteStream = IntStream.iterate(0, i -> i + 2);
// Terminates at first match of i <= 50
boolean test = stream.anyMatch(i -> i <= 50)