Java 8 - потоковая идеология

Недавно я начал играть с Java 8, ранее делал бит и куски в Haskell/ Scala. Я пытаюсь играть с функциями более высокого порядка на Java, например, map или forEach, и я изо всех сил пытаюсь понять, какой мотивацией он должен был подтолкнуть все к идеологии Stream. Я понимаю, что это дает хорошую общую абстракцию, она должна быть ленивой, но рассмотрим очень простой, общий пример:

list.map(x -> do_sth(x));

очень распространенная идиома, ожидая, что это вернет a List<T>. Теперь, в Java 8, мне нужно сделать sth этого типа:

list.stream().map(x -> doSth(x)).collect(Collectors.toList())

Теперь, насколько я вижу это, поток не будет применять карту до тех пор, пока не будет вызван сбор, поэтому будет проходить один проход через коллекцию под капотом. Я не вижу, почему эти общие примеры использования для карт, списки, такие как map.toList(), list.groupBy(), не будут добавлены в соответствующие интерфейсы? Есть ли базовое дизайнерское решение, которое я здесь отсутствует?

Ответы

Ответ 1

Несколько новых методов были добавлены непосредственно в различные коллекции, которые охотно выполняют мутационные операции над этими коллекциями. Например, чтобы запустить функцию над каждым элементом списка, заменив исходные элементы на возвращаемые значения, используйте List.replaceAll(UnaryOperator). Другими примерами этого являются Collection.removeIf(Predicate), List.sort() и Map.replaceAll(BiFunction).

В отличие от этого, в поток добавляется множество новых методов, таких как фильтр, карта, пропуск, лимит, сортировка, отчетность и т.д. Большинство из них ленивы и не мутируют исходный код, а вместо этого передают элементы вниз по течению. Мы рассматривали возможность добавления их непосредственно в классы коллекций. В этот момент возникло несколько вопросов. Как мы отличаем нетерпеливые, мутирующие операции от ленивых, поточно-продуктивных операций? Перегрузка сложна, потому что у них будут разные типы возврата, поэтому они должны иметь разные имена. Как бы такие операции были связаны цепью? Операциям с нетерпением придется создавать коллекции для хранения промежуточных результатов, что потенциально довольно дорого. Полученный API-интерфейс коллекции будет иметь запутанную смесь нетерпеливых, мутирующих и ленивых, не мутирующих методов.

Рассмотрение второго порядка является потенциальной несовместимостью с добавлением методов по умолчанию. Самый большой риск при добавлении методов по умолчанию к интерфейсу - это столкновение имен с существующим методом при реализации этого интерфейса. Если метод с тем же именем и аргументами (часто без аргументов) имеет другой тип возврата, это неизбежная несовместимость. По этой причине мы довольно неохотно добавляем большое количество стандартных методов.

По этим причинам мы решили сохранить ленивые, не мутирующие методы в рамках API потока, за счет необходимости дополнительных вызовов методов stream() и collect() для объединения между коллекциями и потоками. Для нескольких распространенных случаев мы добавили нетерпеливые, мутирующие вызовы непосредственно к интерфейсам коллекций, таким как перечисленные выше.

Подробнее см. lambdafaq.org.