Есть ли какая-либо опасность в выполнении действия.accept() более чем одним элементом реализации Spliterator.tryAdance()?

Javadoc of Spliterator упоминает, что:

Spliterator может проходить элементы по отдельности (tryAdvance()) или последовательно навалом (forEachRemaining()).

Затем мы идем в JavadoctryAdvance() что говорит о том, что:

Если оставшийся элемент существует, выполняет с ним указанное действие, возвращая true; иначе возвращает ложь.

Может быть, я где-то неправильно читаю, но мне кажется, что при условии, что есть один элемент, или болееConsumer в качестве аргумента должен только каждый .accept() один аргумент перед возвращением trueи что если, скажем, у меня есть сразу два аргумента, то я не могу:

action.accept(arg1);
action.accept(arg2);
return true;

В этом проекте я переписал первый сплитератор ширины так, чтобы он теперь читал:

// deque is a Deque<Iterator<T>>

@Override
public boolean tryAdvance(final Consumer<? super T> action)
{
    Iterator<T> iterator;
    T element;

    while (!deque.isEmpty()) {
        iterator = deque.removeFirst();
        while (iterator.hasNext()) {
            element = iterator.next();
            deque.add(fn.apply(element));
            action.accept(element);
        }
    }
    return false;
}

Короче я делаю action примите все аргументы, а затем верните false... и тест, хотя и довольно простой, все еще успешно ( ссылка).

Обратите внимание, что .trySplit() всегда возвращается null; и у spliterator есть характеристики DISTINCT, ORDERED а также NONNULL,

Итак, есть ли использование потока, при котором приведенный выше код не будет работать из-за описанного выше метода, потребляющего все элементы одновременно?

1 ответ

Решение

Ваше предположение, что tryAdvance() должен потреблять только один элемент правильно. Это, однако, не означает, что вы немедленно заметите нарушения договора. Когда вы тестируете, используя такие операции, как .collect(Collectors.toList()) даже маловероятно обнаружить такое нарушение, так как большинство операций, использующих все элементы, будут вызывать forEachRemaining() на сплитераторе которого default реализация документируется как:

Реализация по умолчанию неоднократно вызывает tryAdvance(java.util.function.Consumer), пока не вернет false.

Очевидно, что для этого метода это не имеет значения.

Фреймворк Stream будет вызывать tryAdvance() при выполнении ленивых операций. Поэтому, когда вы используете .peek(System.out::println).findFirst() вы можете заметить разницу, когда ваш tryAdvance() реализация выдвигает более одного значения. Тем не менее, учитывая текущую реализацию, результат является правильным первым элементом. Очевидно, потребитель, предоставленный реализацией, игнорирует последующие значения после обнаружения значения.

Это может быть связано с другими деталями реализации, такими как обсуждаемые в разделе "Почему filter() после flatMap()" не полностью "ленив в потоках Java?". Если сама реализация потока выдвигает больше значений, чем необходимо при определенных обстоятельствах, принимающая сторона в той же реализации должна быть готова обработать этот случай.

Но следует подчеркнуть, что это поведение одной конкретной реализации Stream API. Другая реализация или следующая версия Java могут полагаться на правильную реализацию tryAdvance, Кроме того, могут быть случаи использования для Spliterator с, кроме потоков.


Ну, я наконец нашел пример операции, которая порождает Spliterator:

for(Iterator<?> it=Spliterators.iterator(spliterator); it.hasNext();) {
    System.out.println(it.next());
}
Другие вопросы по тегам