Чому Iterable <T> не забезпечує потокові () та паралельніStream () методи?


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>, щоб ви могли використовувати розширені для-циклів, якщо хочете перетворити цикл на нього. (Мій 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
Я думаю, це гарне запитання до списку розсилки Lambda .
Едвін Далорцо

Чому дивно повторювати потік? Як ще ви могли, можливо break;, повторити? (Гаразд, Stream.findFirst()може бути рішенням, але це може не задовольнити всі потреби ...)
glglgl

Див. Також Перетворення Iterable в Stream за допомогою Java 8 JDK для практичного вирішення.
Вадим

Відповіді:


298

Це не був упущенням; в червні 2013 року було детально обговорено перелік ЕГ.

Остаточне обговорення Експертної групи проводиться в цій темі .

Хоча це здавалося "очевидним" (навіть для Експертної групи спочатку), що, stream()здавалося, має сенс Iterable, факт, який Iterableбув настільки загальним, став проблемою, оскільки очевидний підпис:

Stream<T> stream()

не завжди було те, чого ти хотів. Наприклад, деякі речі, які мали Iterable<Integer>б скоріше повернути їх метод потоку IntStream. Але застосування цього stream()методу високо в ієрархії зробило б це неможливим. Тож замість цього ми зробили дійсно легко виготовити Streamз Iterable, запропонувавши spliterator()метод. Реалізація stream()in 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? Або ви, мабуть, маєте на увазі інший футляр?
skiwi

139
Мені здається дивним, що вищезазначена логіка була нібито застосована до Iterable (я не можу мати потік (), тому що хтось може захотіти, щоб він повернув IntStream), тоді як рівнозначна кількість думок не надходила до додавання такого ж методу до колекції ( Я, можливо, хотів, щоб потік моєї колекції <Integer> () також повернув IntStream.) Будь він присутній на обох або відсутній на обох, люди, ймовірно, просто почали би своє життя, але тому що він присутній на одному і відсутній на з іншого, це стає досить кричущим упущенням ...
Трейказ

6
Брайан Маккотчон: Це має для мене більше сенсу. Здається, що люди просто втомилися сперечатися і вирішили пограти в безпеку.
Джонатан Локк

44
Хоча це має сенс, чи є причина, чому немає альтернативної статики Stream.of(Iterable), яка, принаймні, зробила б метод зрозумілим для ознайомлення з документацією API - як хтось, хто ніколи не працював із внутрішніми потоками, я б ніколи не навіть дивився на StreamSupport, який описаний в документації, забезпечуючи «низькорівневі операції», які « в основному для бібліотеки письменників».
Жуль

9
Я повністю згоден з Жулем. статичний метод Stream.of (Iteratable iter) або Stream.of (Iterator iter) слід додати замість StreamSupport.stream (iter.spliterator (), false);
user_3380739

23

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

Я поки не знайшов задовільного пояснення. Прочитавши все це, я зробив висновок, що це був лише упущення. Але ви можете бачити, що це обговорювалося кілька років протягом проекту API.

Спеціалісти з ламбда-вуст

Я знайшов дискусію з цього приводу у списку розсилки спеціалістів Lambda Libs :

Під Iterable / Iterator.stream () Сем Пуллара сказав:

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

Моя пропозиція - принаймні мати його на Ітераторі, щоб ви могли чисто рухатися між двома світами, і це також було б легко виявити, а не робити:

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

І тоді Брайан Гец відповів :

Я думаю, що Сем наголосив на тому, що існує багато бібліотечних класів, які дають вам ітератор, але не дозволяють вам обов'язково писати власний сплітератор. Отже, все, що ви можете зробити, це потік викликів (spliteratorUnknownSize (ітератор)). Сем пропонує запропонувати Iterator.stream () зробити це для вас.

Я хотів би зберегти потокові () та spliterator () методи як для бібліотечних авторів / досвідчених користувачів.

І пізніше

"З огляду на те, що писати Spliterator простіше, ніж писати Iterator, я вважаю за краще просто написати Spliterator замість Iterator (Iterator так 90-х років :)"

Ти, однак, тобі не вистачає точки. Існують мільйони класів, які вже передають вам Ітератор. І багато з них не готові до розбризкування.

Попередні дискусії у списку розсилки лямбда

Можливо, це не відповідь, яку ви шукаєте, але в списку розсилки Project Lambda це було коротко обговорено. Можливо, це допомагає сприяти більш широкій дискусії з цього питання.

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

Відступаючи ...

Існує маса способів створення потоку. Чим більше інформації ви маєте про опис елементів, тим більше функціональності та продуктивності може надати вам бібліотека потоків. Для того, щоб принаймні отримати більшість інформації, вони:

Ітератор

Ітератор + розмір

Сплитератор

Сплитератор, який знає його розмір

Сплитератор, який знає його розмір, і далі знає, що всі підрозколи знають їх розмір.

(Дехто може бути здивований, виявивши, що ми можемо отримати паралелізм навіть з німого ітератора в тих випадках, коли Q (робота на елемент) нетривіальний.)

Якщо у Iterable був метод stream (), він би просто загортав Iterator зі Spliterator, без інформації про розмір. Але більшість речей, які є Ітерабельними , мають інформацію про розмір. Що означає, що ми обслуговуємо дефіцитні потоки. Це не так добре.

Одним із недоліків практики, описаної Стівеном тут, щодо прийняття Iterable замість колекції, є те, що ви змушуєте речі проходити через «маленьку трубу» і, таким чином, відкидаючи інформацію про розмір, коли це може бути корисним. Це добре, якщо все, що ви робите, - цеБудьте, але якщо ви хочете зробити більше, краще, якщо ви зможете зберегти всю інформацію, яку ви хочете.

За замовчуванням, наданий Iterable, справді був би хитрим - він би відкинув розмір, навіть якщо переважна більшість інтерабелів знає цю інформацію.

Протиріччя?

Хоча, схоже, дискусія ґрунтується на змінах, проведених Експертною групою в початковому дизайні потоків, який спочатку базувався на ітераторах.

Тим не менш, цікаво помітити, що в інтерфейсі, як Collection, метод потоку визначається як:

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

Що може бути точно тим же кодом, який використовується в Iterable інтерфейсі.

Отже, саме тому я сказав, що ця відповідь, ймовірно, не є задовільною, але все ж цікава для обговорення.

Докази рефакторингу

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

Потягніть splitIterator з колекції на Iterable .

Висновок / теорії?

Тоді ймовірність полягає в тому, що відсутність методу в Iterable - це лише упущення, оскільки, схоже, вони повинні були перенести метод потоку також, коли вони перемістили splitIterator з колекції на Iterable.

Якщо є інші причини, вони не є очевидними. Хтось інший має інші теорії?


Я ціную вашу відповідь, але я не згоден з міркуваннями там. В той момент, перекручування spliterator()з Iterable, то всі питання там фіксуються, і ви можете тривіальним реалізувати stream()і parallelStream()..
skiwi

@skiwi Тому я сказав, що це, мабуть, не відповідь. Я просто намагаюся додати до дискусії, тому що важко знати, чому експертна група приймала рішення, які вони зробили. Я думаю, що все, що ми можемо, - це спробувати зробити деякі криміналістики у списку розсилки та побачити, чи зможемо ми знайти якісь причини.
Едвін Далорцо

1
@skiwi Я переглянув інші списки розсилки та знайшов більше доказів для обговорення та, можливо, деяких ідей, які допомагають теоретизувати деякий діагноз.
Едвін Далорцо

Дякую за ваші зусилля, я дійсно повинен навчитися ефективно розподіляти списки розсилки. Це допоможе, якщо їх можна візуалізувати якимось сучасним способом, як-от форум чи щось подібне, адже читання простих текстових електронних листів із цитатами в них не зовсім ефективно.
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().

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.