Search code examples
javagenericsjava-8java-streamcollectors

Java 8 streams: Shorthand for myList.stream().map(Foo::bar).collect(Collectors.toList())


Is there a common shorthand for the following? External dependencies like Guava welcome.

myList.stream().map(Foo::bar).collect(Collectors.toList());

If I had to implement it, it'd be something like:

static <T, U> List<U> mapApply(List<T> list, Function<T, U> function) {
    return list.stream().map(function).collect(Collectors.toList());
}

Is there one that works for any Iterable? If not, how would I write one? I started thinking about it like this:

static <T, U, V extends Iterable> V<U> mapApply(V<T> iterable, Function<T, U> function) {
    return iterable.stream().map(function).collect(???);
}

Solution

  • In the case Foo::bar returns an instance of Foo again, ie. you need to transform T into T again, then you can use List::replaceAll which uses UnaryOperator<T>, therefore each item is replaced by one of a same type. This solution mutates the original list.

    List<String> list = Arrays.asList("John", "Mark", "Pepe");
    list.replaceAll(s -> "hello " + s);
    

    If you want to transform T into R, all you can do is to either use your current solution with a sequence of stream() -> map() -> collect() method calls or a simple iteration.

    A static method wrapping this would do the same as well. Note that you cannot create a Stream from both Collection and Iterable using the same way. Feel free to pass also your custom Collector.

    • T is a generic type of an input Collection or Iterable.
    • R is a generic type of the mapping function result (mapping from T to R)

    From Collection<T>

    List<Bar> listBarFromCollection = mapApply(collectionFoo, Foo::bar, Collectors.toList());
    
    static <T, R> List<R> mapApply(Collection<T> collection, Function<T, R> function) {
        return collection.stream()
            .map(function)
            .collect(Collectors.toList());
    }
    

    From Iterable<T>

    List<Bar> listBarFromIterable = mapApply(iterableFoo, Foo::bar, Collectors.toList());
    
    static <T, R> List<R> mapApply(Iterable<T> iterable, Function<T, R> function) {
        return StreamSupport.stream(iterable.spliterator(), false)
            .map(function)
            .collect(Collectors.toList());
    }
    

    ... with a Collector:

    If you want to pass a custom Collector, it would be Collector<R, ?, U> collector and the return type of the method U instead of List<R>. As @Holger pointed out, passing a Collector to a method would not much differ from calling an actual stream() -> map() -> collect().