A handful of new methods have been added directly to various collections that perform mutative operations eagerly on those collections. For example, to run a function over each element of a list, replacing the original elements with the return values, use List.replaceAll(UnaryOperator)
. Other examples of this are Collection.removeIf(Predicate)
, List.sort()
, and Map.replaceAll(BiFunction)
.
By contrast, there are a bunch of new methods added to Stream such as filter, map, skip, limit, sorted, distinct, etc. Most of these are lazy and they do not mutate the source, instead passing elements downstream. We did consider adding these directly to the collections classes. At this point several questions arose. How do we distinguish the eager, mutative operations from the lazy, stream-producting operations? Overloading is difficult, because they'd have different return types, so they'd have to have different names. How would such operations be chained? The eager operations would have to generate collections to store intermediate results, which is potentially quite expensive. The resulting collections API would have a confusing mixture of eager, mutative and lazy, non-mutating methods.
A second order consideration is potential incompatibilities with adding default methods. The biggest risk with adding default methods to an interface is a name clash with an existing method on an implementation of that interface. If a method with the same name and arguments (often no arguments) has a different return type, that's an unavoidable incompatibility. For that reason we've been fairly reluctant to add large numbers of default methods.
For these reasons, we decided to keep the lazy, non-mutating methods all within the stream API, at the expense of requiring extra method calls stream() and collect() to bridge between collections and streams. For a few common cases we added eager, mutating calls directly to the collections interfaces, such as those I listed above.
See lambdafaq.org for further discussion.