Почему java.util.Collection не реализует новый интерфейс Stream?

Мне просто потребовалось некоторое время, чтобы начать изучать шум java-8 о потоках и лямбдах. Меня удивило, что вы не можете применять операции Stream, такие как .map(), .filter() непосредственно на java.util.Collection. Существует ли техническая причина, по которой интерфейс java.util.Collection не был расширен с помощью по умолчанию реализации этих операций Stream?

Пойдем немного, я вижу множество примеров кодирования людей по шаблону:

List<String> list = someListExpression;
List<String> anotherList = list.stream().map(x -> f(x)).collect(Collectors.toList());

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

List<String> list = someListExpression;
List<String> anotherList = list.map(x -> f(x));

Ответ 1

Да, есть отличные причины для этих решений:)

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

 Optional<Person> tallestGuy = people.stream()
                                     .filter(p -> p.getGender() == MALE)
                                     .max(comparing(Person::getHeight));

операции фильтрации и уменьшения (max) объединяются в один проход. Это очень эффективно.

Итак, почему бы не разоблачить методы Stream прямо в List? Ну, мы попробовали это так. Среди множества других причин мы обнаружили, что смешивание ленивых методов, таких как filter() и нетерпеливые методы, такие как removeAll(), сбивает с толку пользователей. Сгруппировав ленивые методы в отдельную абстракцию, она становится намного яснее; методы на List - это те, которые мутируют список; методы на Stream - это те, которые относятся к композиционным, ленивым операциям с последовательностями данных независимо от того, где эти данные живут.

Итак, способ, которым вы предлагаете, здорово, если вы хотите делать действительно простые вещи, но начинает разваливаться, когда вы пытаетесь построить на нем. Является ли дополнительный метод stream() раздражающим? Конечно. Но сохранение абстракций для структур данных (которые в основном связаны с организацией данных в памяти) и потоков (которые в основном связаны с составлением совокупного поведения) раздельно масштабируются для более сложных операций.

К вашему второму вопросу вы можете сделать это относительно легко: реализовать потоковые методы следующим образом:

public<U> Stream<U> map(Function<T,U> mapper) { return convertToStream().map(mapper); }

Но это просто плавание против прилива; лучше просто реализовать эффективный метод stream().