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

241

Я задаюсь вопросом, почему 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, если хотите зациклить его. (Мой 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>?
Что довольно странно, предлагая сделать это несколько иначе.

skiwi
источник
1
Я думаю, что это хороший вопрос для Lambda Mailing List .
Эдвин Далорсо
Почему странно хотеть перебирать поток? Как еще вы могли бы break;сделать итерацию? (Хорошо, Stream.findFirst()возможно, это решение, но оно не может удовлетворить все потребности ...)
glglgl
См. Также Преобразование итерируемого в поток с использованием Java 8 JDK для практических обходных путей.
Вадим

Ответы:

298

Это не было упущением; в июне 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будет ошибкой.

Брайан Гетц
источник
8
Я вижу, прежде всего, большое спасибо за ответ на вопрос. Мне все еще любопытно, хотя почему Iterable<Integer>(я думаю, вы говорите?) Хотели бы вернуть IntStream. Будет ли итерируемый, а не PrimitiveIterator.OfInt? Или вы имеете в виду другой вариант использования?
Скиви
139
Я нахожу странным, что вышеупомянутая логика предположительно была применена к Iterable (у меня не может быть stream (), потому что кто-то может захотеть, чтобы он возвращал IntStream), тогда как при добавлении точно такого же метода в Collection (не было) Возможно, я бы хотел, чтобы stream <Integer> моей коллекции также возвращал IntStream.) Независимо от того, присутствовали ли они на обоих или отсутствовали на обоих, люди, вероятно, просто продолжили бы свою жизнь, но поскольку они присутствуют на одном и отсутствуют на обоих. с другой стороны, это становится довольно
явным
6
Брайан Маккатон: Для меня это имеет больше смысла. Похоже, люди просто устали спорить и решили не рисковать.
Джонатан Лок
44
Хотя это имеет смысл, есть причина, по которой нет альтернативной статики Stream.of(Iterable), которая, по крайней мере, сделала бы метод разумно доступным для обнаружения, читая документацию по API - как тот, кто никогда не работал с внутренними компонентами потоков, я бы никогда не стал даже смотрел на StreamSupport, который описан в документации, обеспечивая «низкоуровневые операции», которые « в основном для библиотеки писателей».
Жюль
9
Я полностью согласен с Жюлем. вместо StreamSupport.stream (iter.spliterator (), false) следует добавить статический метод Stream.of (Iteratable iter) или Stream.of (Itera iter).
user_3380739
23

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

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

Специалисты по Lambda Libs Spec

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

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

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

Я предлагаю по крайней мере иметь его на Iterator, чтобы вы могли аккуратно перемещаться между двумя мирами, и это также было бы легко обнаружить, вместо того, чтобы делать это:

Streams.stream (Spliterators.spliteratorUnknownSize (iterator, Spliterator.ORDERED))

И тогда Брайан Гетц ответил :

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

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

И позже

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

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

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

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

По словам Брайана Гетца в разделе « Потоки из Итерибла» :

Отступая ...

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

Итератор

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

Spliterator

Spliterator, который знает его размер

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

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

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

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

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

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

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

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

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

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

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

Свидетельство Рефакторинга

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

Вытяните splitIterator из коллекции в итерируемую .

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

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

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

Эдвин Далорсо
источник
Я ценю ваш ответ, но я не согласен с доводами там. В тот момент, когда вы переопределить spliterator()изIterable , то все вопросы там фиксируются, и вы можете тривиальным реализовать stream()и parallelStream()..
skiwi
@skiwi Вот почему я сказал, что это, вероятно, не ответ. Я просто пытаюсь добавить к дискуссии, потому что трудно понять, почему группа экспертов приняла решения, которые они приняли. Полагаю, все, что мы можем сделать, - это попробовать провести экспертизу в списке рассылки и посмотреть, сможем ли мы найти какие-либо причины.
Эдвин Далорсо
1
@skiwi Я просмотрел другие списки рассылки и нашел больше доказательств для обсуждения и, возможно, некоторые идеи, которые помогают теоретизировать некоторые диагнозы.
Эдвин Далорсо
Спасибо за ваши усилия, я должен действительно научиться эффективно разделять эти списки рассылки. Было бы полезно, если бы они могли быть визуализированы каким-то ... современным способом, например, форумом или чем-то еще, потому что чтение электронных писем в виде простого текста с кавычками в них не совсем эффективно.
Скиви
6

Если вы знаете размер, который вы можете использовать, 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().

Ты делаешь
источник