Вызовы потоковых операций Java

Может ли кто-нибудь указать на официальную документацию по Java, в которой описано, сколько раз Stream будет вызывать каждую промежуточную операцию "без вмешательства и без сохранения состояния" для каждого элемента.

Например:

Arrays.asList("1", "2", "3", "4").stream()
        .filter(s -> check(s))
        .forEach(s -> System.out.println(s));

public boolean check(Object o) {
    return true;
} 

Вышеупомянутый в настоящее время вызовет метод check 4 раза.

Возможно ли, что в текущих или будущих версиях JDK метод check выполняется более или менее раз, чем количество элементов в потоке, созданном из List или любого другого стандартного Java API?

Ответ 1

Это связано не с источником потока, а с работой терминала и оптимизацией, выполняемой в самой реализации потока. Например:

Stream.of(1,2,3,4)
      .map(x -> x + 1)
      .count();

Начиная с java-9, map не будет выполнена ни разу.

Или же:

 someTreeSet.stream()
            .sorted()
            .findFirst();

sorted может не выполняться вообще, поскольку источником является TreeSet а получение первого элемента тривиально, но если это реализовано внутри потокового API или нет, это другой вопрос.

Так что реальный ответ здесь - это зависит, но я не могу представить одну операцию, которая выполнялась бы больше, чем количество элементов в источнике.

Ответ 2

Из документации:

Лень-поиск. Многие потоковые операции, такие как фильтрация, отображение или удаление дубликатов, могут быть реализованы лениво, открывая возможности для оптимизации. Например, "найти первую строку с тремя последовательными гласными" не нужно проверять все входные строки. Потоковые операции делятся на промежуточные (генерирующие поток) операции и терминальные (value- или производящие побочный эффект) операции. Промежуточные операции всегда ленивы.

Благодаря этому, поскольку filter является промежуточной операцией, которая создает новый Stream как часть своей операции, из-за своей лени он будет вызывать предикат фильтра только один раз для каждого элемента в рамках своей перестройки потока.

Единственный способ, которым ваш метод мог бы иметь другое количество вызовов против него в потоке, - это если бы поток каким-то образом мутировал между состояниями, что с учетом того факта, что ничего в потоке фактически не выполняется до операции терминала, было бы только реально возможным из-за ошибки вверх по течению.