Почему Iterable <T> не предоставляет методы stream() и parallelStream()?

Мне интересно, почему интерфейс Iterable не предоставляет методы stream() и parallelStream(). Рассмотрим следующий класс:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}

Это реализация Руки, так как вы можете иметь карты в руке во время игры с карточной карточкой.

По существу он обертывает List<Card>, обеспечивает максимальную емкость и предлагает некоторые другие полезные функции. Лучше реализовать его непосредственно как List<Card>.

Теперь, для уверенности, я подумал, что было бы неплохо реализовать Iterable<Card>, чтобы вы могли использовать расширенные for-loops, если вы хотите перебрать его. (Мой класс Hand также предоставляет метод get(int index), поэтому Iterable<Card> оправдан по моему мнению.)

Интерфейс Iterable предоставляет следующее (исключено javadoc):

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Теперь вы можете получить поток с помощью:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

Итак, на реальный вопрос:

  • Почему Iterable<T> не предоставляет методы по умолчанию, которые реализуют stream() и parallelStream(), я не вижу ничего, что сделало бы это невозможным или нежелательным?

Связанный вопрос, который я нашел, следующий: Почему Stream <T> не реализовать Iterable <T> ?
Что, как ни странно, говорит о том, чтобы сделать это несколько наоборот.

Ответ 1

Это не было упущением; было детальное обсуждение списка EG в июне 2013 года.

Окончательное обсуждение экспертной группы основано на этой теме.

В то время как казалось очевидным (даже для Группы экспертов), что stream(), казалось, имел смысл в Iterable, то, что Iterable был настолько общим, стал проблемой, потому что очевидная сигнатура:

Stream<T> stream()

не всегда было то, что вы хотели. Некоторые вещи, которые были Iterable<Integer>, предпочли бы, чтобы их метод потока возвращал IntStream, например. Но приведение метода stream() к высокому уровню иерархии сделает невозможным. Поэтому вместо этого мы сделали очень легким сделать Stream из Iterable, предоставив метод spliterator(). Реализация stream() в Collection справедлива:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Любой клиент может получить поток, который они хотят от Iterable, с помощью:

Stream s = StreamSupport.stream(iter.spliterator(), false);

В заключение мы пришли к выводу, что добавление stream() в Iterable было бы ошибкой.

Ответ 2

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

Я пока не нашел удовлетворительного объяснения. Прочитав все это, я пришел к выводу, что это просто упущение. Но вы можете видеть здесь, что он обсуждался несколько раз в течение многих лет при разработке API.

Эксперты экспертов Lambda Libs

Я нашел дискуссию об этом в списке рассылки экспертов Lambda Libs Spec:

В Iterable/Iterator.stream() Сэм Пуллара сказал:

Я работал с Брайаном, видя, как limit/substream функциональность [1] может быть реализована, и он предложил преобразование в Итератор был правильным путем. Я думал об этом но не нашел очевидного способа взять итератор и повернуть это в поток. Оказывается, он там, вам просто нужно сначала конвертировать итератор в разделитель, а затем преобразовать разделитель к потоку. Поэтому это приводит меня к пересмотру того, следует ли нам они свисают с одного из Iterable/Iterator напрямую или обоих.

Мое предложение - по крайней мере, на Итераторе, чтобы вы могли двигаться чисто между двумя мирами, и это также было бы легко видимо, а не делать:

Streams.stream(Spliterators.spliteratorUnknownSize(итератор, Spliterator.ORDERED))

И затем Брайан Гетц ответил:

Я думаю, что точка Сэм заключалась в том, что есть много классов библиотек, которые дать вам Итератор, но не позволяйте вам обязательно писать свои собственные spliterator. Так что все, что вы можете сделать, это позвонить Поток (spliteratorUnknownSize (итератор)). Сэм предлагает нам определите Iterator.stream(), чтобы сделать это для вас.

Я хотел бы сохранить методы stream() и spliterator() как для авторов библиотек/продвинутых пользователей.

И позже

"Учитывая, что писать Spliterator проще, чем писать Iterator, Я бы предпочел просто написать Spliterator вместо Iterator (Iterator - это 90s:)"

Однако вам не хватает смысла. Есть два уровня классов там, что уже вручил вам Итератор. И многие из них не spliterator готовности.

Предыдущие обсуждения в списке рассылки Lambda

Это может быть не тот ответ, который вы ищете, но в Project Lambda listing list это было кратко обсуждено. Возможно, это помогает способствовать более широкому обсуждению этого вопроса.

По словам Брайана Гетца под Потоки из Iterable:

Отступив назад...

Существует множество способов создания потока. Чем больше информации вы имеют о том, как описывать элементы, тем больше функциональности и производительность библиотеки потоков может дать вам. В порядке наименьшего большинство информации:

итератора

Итератор + размер

Spliterator

Разделитель, который знает его размер

Разделитель, который знает его размер, и далее знает, что все подсечки знать их размер.

(Некоторые могут быть удивлены, обнаружив, что мы можем извлечь parallelism даже из немого итератора в случаях, когда Q (произведение на элемент) нетривиальный.)

Если у Iterable был метод stream(), он просто завернул бы Iterator с помощью Spliterator, без информации о размере. Но большинство вещей, которые Iterable имеет информацию о размере. Это означает, что мы обслуживаем недостаточные потоки. Это не так хорошо.

Один недостаток практики API, описанный Стивеном здесь, из принятие Iterable вместо Collection, заключается в том, что вы вынуждаете вещи через "небольшую трубу" и, следовательно, отбрасывают размер когда это может быть полезно. Это прекрасно, если все, что вы делаете делать это для каждого, но если вы хотите сделать больше, то лучше, если вы может сохранить всю необходимую информацию.

Значение по умолчанию, предоставленное Iterable, действительно было бы дерьмовым - это отбросит размер, несмотря на то, что подавляющее большинство Iterables знают эту информацию.

Противоречие?

Хотя, похоже, что обсуждение основано на изменениях, внесенных Группой экспертов в первоначальный проект Streams, который первоначально был основан на итераторах.

Тем не менее, интересно заметить, что в интерфейсе, таком как Collection, метод потока определяется как:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

Какой может быть тот же самый код, который используется в интерфейсе Iterable.

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

Доказательства рефакторинга

Продолжая анализ в списке рассылки, похоже, что метод splitIterator изначально был в интерфейсе Collection, а в какой-то момент 2013 года он переместил его в Iterable.

Потяните splitIterator из коллекции в Iterable.

Заключение/Теории?

Тогда есть вероятность, что отсутствие метода в Iterable - это просто упущение, поскольку похоже, что они также должны были перемещать метод потока, когда они перемещали splitIterator из Collection в Iterable.

Если есть другие причины, это не очевидно. У кого-то есть другие теории?

Ответ 3

Если вы знаете размер, который вы могли бы использовать java.util.Collection, который предоставляет метод stream():

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

И затем:

new Hand().stream().map(...)

Я столкнулся с той же проблемой и был удивлен, что реализация Iterable может быть очень легко распространена на реализацию AbstractCollection, просто добавив метод size() (к счастью, у меня был размер коллекции: -)

Вы также должны рассмотреть возможность переопределения Spliterator<E> spliterator().