Почему Collection‹T› не реализует Stream‹T›?

Это вопрос о дизайне API. Когда в C# были добавлены методы расширения, IEnumerable получили все методы, позволяющие использовать лямбда-выражения непосредственно во всех коллекциях.

С появлением лямбда-выражений и методов по умолчанию в Java я ожидал, что Collection будет реализовывать Stream и предоставлять реализации по умолчанию для всех его методов. Таким образом, нам не нужно будет вызывать stream(), чтобы использовать предоставляемую им мощность.

По какой причине архитекторы библиотек выбрали менее удобный подход?


person Vitaliy    schedule 11.02.2015    source источник
comment
является ли коллекция потоком?   -  person UmNyobe    schedule 11.02.2015
comment
Да, это. Но не наоборот. И даже если это порождает философские споры, это имеет большой практический смысл. Как я уже говорил, это прекрасно работало на C#.   -  person Vitaliy    schedule 11.02.2015
comment
На мой взгляд, основное различие заключается в следующем: предполагается, что поток полезен для операций с элементами, тогда как коллекция предназначена для хранения элементов.   -  person Tobias    schedule 11.02.2015
comment
@still_learning Я тоже так вижу.   -  person UmNyobe    schedule 11.02.2015
comment
Ответ одного из архитекторов библиотеки   -  person Alex - GlassEditor.com    schedule 11.02.2015
comment
Но коллекция также используется для извлечения элементов. И сам поиск может быть тесно связан с деталями внутренней реализации коллекции.   -  person Vitaliy    schedule 12.02.2015


Ответы (4)


Из часто задаваемых вопросов Мориса Нафталина по Lambda:

Почему операции Stream не определены непосредственно в Collection?

В ранних проектах API были доступны такие методы, как filter, map и reduce для Collection или Iterable. Однако пользовательский опыт с этим дизайном привел к более формальному разделению «потоковых» методов на их собственную абстракцию. Среди причин:

  • Методы Collection, такие как removeAll, вносят изменения на месте, в отличие от новых методов, которые по своей природе более функциональны. Смешивание двух разных методов в одной и той же абстракции заставляет пользователя отслеживать, какие из них какие. Например, при объявлении

    Collection strings;
    

    два очень похожих вызова метода

    strings.removeAll(s -> s.length() == 0);
    strings.filter(s -> s.length() == 0);          // not supported in the current API
    

    были бы удивительно разные результаты; первый удалит из коллекции все пустые объекты String, тогда как второй вернет поток, содержащий все непустые объекты String, но не окажет никакого влияния на коллекцию.

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

    strings.stream().filter(s.length() == 0)...;
    

    где многоточие представляет дальнейшие потоковые операции, заканчивающиеся завершающей операцией. Это дает читателю гораздо более ясное представление о действии фильтра;

  • С добавлением ленивых методов в Collection пользователи были сбиты с толку кажущейся — но ошибочной — необходимостью рассуждать о том, находится ли коллекция в «ленивом режиме» или в «режиме нетерпения». Вместо того, чтобы обременять Collection новой функциональностью, лучше предоставить представление Stream с новой функциональностью;

  • Чем больше методов добавлено в Collection, тем больше вероятность конфликтов имен с существующими сторонними реализациями. Добавляя только несколько методов (stream, parallel), вероятность конфликта значительно снижается;

  • Преобразование представления по-прежнему необходимо для доступа к параллельному представлению; асимметрия между последовательным и параллельным представлениями потока была неестественной. Сравните, например

    coll.filter(...).map(...).reduce(...);
    

    с

    coll.parallel().filter(...).map(...).reduce(...);
    

    Эта асимметрия будет особенно очевидна в документации API, где Collection будет иметь много новых методов для создания последовательных потоков, но только один для создания параллельных потоков, которые затем будут иметь все те же методы, что и Collection. Выделение их в отдельный интерфейс, скажем, StreamOps, не поможет; это все равно, вопреки здравому смыслу, должно быть реализовано как Stream, так и Collection;

  • Единая обработка представлений также оставляет место для других дополнительных представлений в будущем.

person John Kugelman    schedule 11.02.2015
comment
Мне кажется немного странным, что это запутало пользователей. В конце концов, разработчиков C# это не смущает (C# List‹T› поддерживает как ленивые, так и нетерпеливые методы). Если для этого ребятам из Java понадобился эксперимент на людях, я думаю, нет лучшего доказательства того, что эта концепция работает. (и дело не в том, что ребята из С# умнее...). О коллекциях, ОК. Что касается параллельных API, я не вижу здесь ничего противоречащего здравому смыслу. Опять же, извините за заезженную пластинку, это именно то, что было сделано на C#. - person Vitaliy; 12.02.2015
comment
Что бы это ни стоило, я согласен с вами. Я понимаю смысл метода stream(), но не всегда с ним согласен. Stream API немного неуклюж, чем должен быть, как с методом stream(), так и со всем механизмом Collector. (Было бы неплохо иметь метод Stream.toList().) Тем не менее, я чувствую себя комфортно, зная, что это было, по крайней мере, обдуманное решение, а не недосмотр. - person John Kugelman; 12.02.2015

  1. Коллекция — это объектная модель.
  2. Поток — это предметная модель

Определение коллекции в документе:

коллекция представляет собой группу объектов, известных как ее элементы.

определение потока в документе:

последовательность элементов, поддерживающих последовательные и параллельные агрегированные операции

С этой точки зрения поток является конкретной коллекцией. Не наоборот. Таким образом, Коллекция не должна реализовывать поток, независимо от обратной совместимости.

Так почему же Stream<T> не реализует Collection<T>? Потому что это еще один способ взглянуть на кучу объектов. Не как группу элементов, а по операциям, которые вы можете над ней выполнять. Вот почему я говорю, что коллекция — это объектная модель, а поток — предметная модель.

person UmNyobe    schedule 11.02.2015

Во-первых, из документации Stream. :

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

Таким образом, вы хотите, чтобы концепции потока и сбора разделялись. Если бы Collection реализовал Stream, каждая коллекция была бы потоком, а концептуально это не так. Как это делается сейчас, каждая коллекция может дать вам поток, который работает с этой коллекцией, а это нечто другое, если подумать.

Другим фактором, который приходит на ум, является сплоченность/связь, а также инкапсуляция. Если бы каждый класс, реализующий Collection, также должен был бы реализовывать операции Stream, он имел бы два (своего рода) разных назначения и мог бы стать слишком длинным.

person André Stannek    schedule 11.02.2015

Я предполагаю, что это было сделано таким образом, чтобы избежать поломки существующего кода, реализующего Collection. Было бы сложно предоставить реализацию по умолчанию, которая корректно работала бы со всеми существующими реализациями.

person John R    schedule 11.02.2015