Why doesn't `List` have a default` map` method when it has `forEach`?

I looked at the code based on threads in Java 8 and noticed a pattern, namely that I often have a list, but I need to convert it to another list, applying a trivial mapping for each element. After writing .stream().map(...).collect(Collections.toList()) again, when I remembered that we had List.forEach , so I was looking for List.map , but apparently this method has not been added by default.

Why is List.map() (EDIT: or List.transform() or List.mumble() ) not added (this is a matter of history) and is there a simple shorthand using other methods in the default runtime library that does the same that I just didn’t notice?

+6
source share
4 answers

Of course, I cannot look into the head of Java designers, but I can think of several reasons not to include map (or other flow methods) in the collection.

  • This is a bloat API. The same can be done in a more general way with little overhead using streams.

  • This leads to bloat code. If I named map in the list, I would expect the result to have the same type of runtime (or at least with the runtime parameters) as the list I called it to. Thus, for ArrayList.map ArrayList , LinkedList.map a LinkedList , etc. will be returned. This means that the same functionality must be implemented in all implementations of List (with a suitable default implementation in the interface, the code will not be broken so old).

  • This will encourage code like list.map(function1).map(function2) , which is significantly less efficient than list.stream().map(function1).map(function2).collect(Collectors.toList()) , because he creates an auxiliary list, which is immediately discarded, and the latter applies both functions to the elements of the list and only then creates a list of results.

For a functional language such as Scala, the balance between advantages and disadvantages may vary.

I don't know the shortcuts in the standard Java library, but you can, of course, implement your own:

 public static <S,T> List<T> mapList(List<S> list, Function<S,T> function) { return list.stream().map(function).collect(Collectors.toList()); } 
+5
source

As explained in “ Why doesn't java.util.Collection implement the new Stream interface? ” The constructive solution for separating Collection API and Stream API was created to separate impatient and lazy operations.

In this regard, several additional operations have been added to the Collection API:

  • List.replaceAll(UnaryOperator)
  • List.sort(Comparator)
  • Map.replaceAll(BiFunction)
  • Collection.removeIf(Predicate)
  • Map.forEach(BiConsumer)
  • Iterable.forEach(Consumer)

Common to all these impatient methods is that functions that evaluate the result are used to modify the underlying collection. The map method that returns a new Iterable or Collection will not fit into the schema.

Among these methods, forEach(Consumer) is the only one that has a signature that matches the Stream method. Unfortunately, these methods do not even do the same; the closest equivalent of Iterable.forEach(Consumer) is Stream.forEachOrdered(Consumer) . But it is also clear why there is functional overlap.

Performing an action for its side effect for each element is the only mass operation that does not change the original collection, so Stream API can also be offered (like a terminal operation). There he would be chained after one or more lazily evaluated intermediate operations; its use without preliminary intermediate operations is a special case.

Since map not a terminal operation, it will not fit into the Collection method scheme at all. The closest equivalent is List.replaceAll(UnaryOperator) .

+4
source

From a conceptual point of view, the forEach method (which, as mentioned in the comments above, specified in the java.lang.Iterable interface), and the map() methods are very different.

The main difference is that java.lang.Iterable#forEach(...) returns nothing, it is void . Therefore, adding it to the Iterable interface with the default implementation does not violate anything, and fits well with the logic of this structure.

So far, java.util.stream.Stream#map(...) returns <R> Stream<R> .

If I were a Iterable interface developer and asked to add a map there, I would first ask: what type should it return? If it is <R> Stream<R> , so Iterable not suitable for it. If not, what else?

I think this is the reason.

UPD : @DavidtenHove suggested why not make this map method return an Iterable<B> , for example:

  default <A, B> Iterable<B> map(Function<A, B> f) { //... } 

My opinion: because in this case the Iterable interface becomes an analog of the Stream interface, which does not make sense.

forEach also duplicates the Stream logic, but it fits the Iterable logic quite well.

+1
source

Simply put: they did not want to put everything in the Stream class in all streamables classes. They just get too big. They put forEach because they are used so often, but drew a line there.

As for the abbreviations, I do not think they are. You just need to use the collect () method with collectors.

0
source

Source: https://habr.com/ru/post/1015141/


All Articles