7

I have looked into writing streams-based code in Java 8, and have noticed a pattern, namely that I frequently have a list but have the need to transform it to another list by applying a trivial mapping to each element. After writing .stream().map(...).collect(Collections.toList()) yet another time I remembered we have List.forEach so I looked for List.map but apparently this default method has not been added.

Why was List.map()(EDIT: or List.transform() or List.mumble()) not added (this is a history question) and is there a simple shorthand using other methods in the default runtime library that does the same thing that I have just not noticed?

Hoopje
  • 12,677
  • 8
  • 34
  • 50
Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
  • what makes you think people would only want to map Lists to Lists? – Patrick Parker Feb 21 '17 at 10:32
  • The `map` operation returns a stream. So you basically want to have `List.map(...)` as a shortcut for `List.stream().map(...)`? – Seelenvirtuose Feb 21 '17 at 10:33
  • I agree with the question. In other languages, such as Scala, this is supported, and very convenient (in my opinion anyway). – David ten Hove Feb 21 '17 at 10:34
  • @PatrickParker I don't. This is why there is a whole arsenal of collectors. This does not prohibit narrower scoped helper methods elsewhere. – Thorbjørn Ravn Andersen Feb 21 '17 at 10:34
  • 2
    `forEach()` comes from `Iterable`, not from `List` (`List` extends `Collection` which extends `Iterable`). – Florent Bayle Feb 21 '17 at 10:34
  • Regardless, it would have been possible to put .map() on Iterable as well. – David ten Hove Feb 21 '17 at 10:34
  • @DavidtenHove that would actually have been a really nice place to have it. – Thorbjørn Ravn Andersen Feb 21 '17 at 10:37
  • @ThorbjørnRavnAndersen I agree and it's one of the reason I really like Scala. You can pretty much map and foreach everything. Lists, arrays, streams, iterables, etc. – David ten Hove Feb 21 '17 at 10:47
  • 2
    That wasn't trolling. I don't consider this question to be very high quality, since it's essentially "In other language there is this functionality, why doesn't Java have it". There's a good historic link to things such as `goto` and why some languages decided to not include it. – Kayaman Feb 21 '17 at 10:47
  • 1
    Short answer: because in this case you have to make all collections extend `Stream` interface, or `Iterable` will duplicate logic of `Stream`. See also my answer. – Andremoniy Feb 21 '17 at 10:51
  • @Andremoniy a list map method does not have to implement streams. It could be a trivially simple implementation, but conveniently placed. – Thorbjørn Ravn Andersen Feb 21 '17 at 11:12
  • @Kayaman And if you read the whole question there was also "Is there a simple method elsewhere in the runtime library that can do this?" which should give you opportunity to provide high quality answer. – Thorbjørn Ravn Andersen Feb 21 '17 at 11:13
  • 1
    @ThorbjørnRavnAndersen why are you still talking about `List`, while it is all in `Iterable`? Looks like we are talking about some imagination... – Andremoniy Feb 21 '17 at 11:13
  • @Andremoniy no, `forEach` is in iterable. I am asking about a default method on a list that can convert it, inspired by forEach. – Thorbjørn Ravn Andersen Feb 21 '17 at 11:24
  • 1
    I don't consider "You're looking for a shortcut that doesn't exist" to be a high quality answer. Java doesn't have array s(p)licing either, and that seems strange to Javascript/Python/Ruby developers. There are probably hundreds of places where you could go "oh it would be nice if I could do X here". But at least according to some claims (reference needed), Java has never really tried to offer "the best of all worlds" which does somewhat reduce the complexity of the language (yeah, right). Or...`List.map()` was just something nobody thought important enough to include. – Kayaman Feb 21 '17 at 11:27

4 Answers4

6

As explained in “Why doesn't java.util.Collection implement the new Stream interface?” the design decision to separate the Collection API and the Stream API was made to separate eager and lazy operations.

In this regard, several bulk operation were 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 eager methods is that functions which evaluate to a result are used to modify the underlying Collection. A map method returning a new Iterable or Collection wouldn’t fit into the scheme.

Further, among these methods, forEach(Consumer) is the only one that happens to have a signature matching a Stream method. Which is unfortunate, as these methods don’t even do the same; the closest equivalent to Iterable.forEach(Consumer) is Stream.forEachOrdered(Consumer). But it is also clear, why there is a functional overlap.

Performing an action for its side effect for each element is the only bulk operation that doesn’t modify the source collection, hence can be offered by the Stream API as well (as a terminal operation). There, it would be chained after one or more lazily evaluated intermediate operations; using it without prepended intermediate operations, is a special case.

Since map isn’t a terminal operation, it wouldn’t fit into the scheme of Collection methods at all. The closest equivalent is List.replaceAll(UnaryOperator).

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
5

Of course, I can't look into the head of the Java designers, but I can think of a number of reasons not to include a map (or other stream methods) on collections.

  1. It's API bloat. The same thing can be done, in a more general way, with minor typing overhead using streams.

  2. It leads to code bloat. If I called map on a list, I would expect the result to have the same runtime type (or at least with the runtime properties) as the list I called it on. So for a ArrayList.map would return an ArrayList, LinkedList.map a LinkedList etc. That means that the same functionality would need to be implemented in all List implementations (with a suitable default implementation in the interface so old code will not be broken).

  3. It would encourage code like list.map(function1).map(function2), which is considerably less efficient than list.stream().map(function1).map(function2).collect(Collectors.toList()) because the former constructs an auxiliary list which is immediately thrown away, while the latter applies both functions to the list elements and only then constructs the result list.

For a functional language like Scala the balance between advantages and disadvantages might be different.

I do not know of shortcuts in the Java standard 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());
}
Hoopje
  • 12,677
  • 8
  • 34
  • 50
1

From conceptual point of view, forEach method (which, as several time it was said in commentaries above declared in java.lang.Iterable interface) and map()methods are very different.

The main difference is that java.lang.Iterable#forEach(...) returns nothing, it is void. So adding it to Iterable interface with default implementation doesn't break anything, and good fits into logic of this structure.

While java.util.stream.Stream#map(...) returns <R> Stream<R>.

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

I believe this is the reason.

UPD: @DavidtenHove suggested, why would not make this map method returning Iterable<B>, like:

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

my opinion: because in this case Iterable interface becomes analogue of Stream interface, what doesn't have sense.

forEach also duplicates logic of Stream, but it fits quite good in Iterable logic.

Andremoniy
  • 34,031
  • 20
  • 135
  • 241
0

Simply put: they didn't want to put everything in the Stream class on all streamables classes. They would simply get too big. They put forEach because it is so commonly used, but drew the line there.

As for the shorthands, I do not believe there are any. You just have to use the collect() method with the collectors.

David ten Hove
  • 2,748
  • 18
  • 33
  • 5
    `forEach` is defined on `Iterable`, not on `List` or `Collection`. Could you provide references for your statements or is this just opinion? – Robby Cornelissen Feb 21 '17 at 10:33
  • 1
    "They...put the line..." . Is this your qualified guess or do you have sources? (I'd like to see the disucssion :) ) – Thorbjørn Ravn Andersen Feb 21 '17 at 10:36
  • 1
    I remember reading about it but don't have the sources readily available right now. But you can take a look for example here: http://stackoverflow.com/questions/1049518/why-does-the-java-list-interface-not-support-getlast and here: https://www.infoq.com/articles/API-Design-Joshua-Bloch – David ten Hove Feb 21 '17 at 10:41