Java 8流:简写为myList.stream().map(Foo::bar).collect(Collectors.toList())

7
有没有以下内容的常见简写形式?欢迎使用Guava等外部依赖。
myList.stream().map(Foo::bar).collect(Collectors.toList());

如果我必须实现它,我的做法可能是这样的:
static <T, U> List<U> mapApply(List<T> list, Function<T, U> function) {
    return list.stream().map(function).collect(Collectors.toList());
}

有没有一个适用于任何可迭代对象的函数?如果没有,我该如何编写?我开始像这样思考:

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

我不知道在原始的JDK中是否存在任何快捷方式。 - Zabuzard
1
在Java中,泛型不能有泛型(V<U>是无效的)。我也很想拥有这个功能! - Felix
你不能将元素收集到一个随机的Iterable中,你要收集的对象必须支持像add等方法。你可以接受任何Collection,然后使用Collectors.toCollection或者直接接受一个Collector,这样就可以完全自定义了。 - Zabuzard
我明白了,谢谢。无论如何,只针对列表和集合的解决方案就足以满足我的需求。我感觉它一定存在,但是在我使用的任何库中都很难找到。在Mathematica中,它作为运算符“/@”是一流公民。 - Andrew Cheong
我认为,任何足够通用以类似于您的第二个“mapApply”方法的解决方案可能只比编写流/映射/收集具有较小的优势。 - Andy Turner
1
你不能保证转换结果的类型与输入类型相同。如果输入是一个 EnumSet<Thread.State>,而映射函数是Thread.State::toString,那该怎么办? - Holger
3个回答

7
在情况下,如果Foo::bar再次返回Foo的实例,即您需要将T再次转换为T,那么您可以使用List::replaceAll来替换每个项,它使用UnaryOperator<T>,因此每个项都会被同一类型的项替换。该解决方案会改变原始列表。
List<String> list = Arrays.asList("John", "Mark", "Pepe");
list.replaceAll(s -> "hello " + s);

如果你想将 `T` 转换成 `R`,你可以使用以下两种方法之一:使用 `stream()` -> `map()` -> `collect()` 方法序列来使用当前解决方案,或者使用简单的迭代。你也可以使用包装此操作的静态方法。请注意,你不能通过相同的方式从 `Collection` 和 `Iterable` 中创建一个 `Stream`,如果需要,可以传递自定义的 `Collector`。
  • T 是输入集合(`Collection`)或可迭代对象(`Iterable`)的通用类型。
  • R 是映射函数结果(从 `T` 映射到 `R` 的结果)的通用类型。

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());
}

来自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());
}

...使用Collector:

如果你想要传递一个自定义的Collector,那么它将会是Collector<R, ?, U> collector,并且该方法的返回类型为U而不是List<R>。正如@Holger指出的那样,将Collector传递给一个方法并不会与调用实际的stream() -> map() -> collect()有太大的区别。


1
当你有可迭代的变量时,就不需要特定于集合的变量。我是否会在项目中创建这些方法取决于我在那里需要它们的频率。我不会为它们创建另一个实用程序库。 - Holger
有道理。感谢您的见解! - Nikolas Charalambidis

1
您可以这样实现该方法:

public class StreamShorthandUtil {
    
    public static <T, U, V extends Collection<T>, W> W mapApply(V in, Function<T, U> function, Collector<U, ?, W> collector) {
        return in.stream().map(function).collect(collector);
    }

    // main class for testing
    public static void main(String[] args) {
        List<String> numbersAsString = Arrays.asList("1", "2", "3", "4", "5");
        List<Integer> numbers = mapApply(numbersAsString, Integer::parseInt, Collectors.toList());
    }
}

这个方法有一个名为Collector的参数,用于定义返回类型,除了输入和映射函数。

4
“Shorthand”指的是使用缩写或简化形式表达某个概念或功能。在代码中,可以使用“Shorthand”来简化代码的书写。例如,在使用Java编程语言时,可以使用以下代码“mapApply(numbersAsString, Integer::parseInt, Collectors.toList())”来代替“numbersAsString.stream().map(Integer::parseInt).collect(Collectors.toList())”。 - Holger

1
你可以使用 Eclipse Collections,它具有丰富的API,直接在集合上使用协变返回类型的方法,如collect(又称map)。
例如:
MutableList<Foo> fooList;
MutableList<Bar> barList = fooList.collect(Foo::bar);

MutableSet<Foo> fooSet;
MutableSet<Bar> barSet = fooSet.collect(Foo::bar);

MutableBag<Foo> fooBag;
MutableBag<Bar> barBag = fooBag.collect(Foo::bar);

Iterate 实用程序类也可以与任何 Iterable 一起使用,并提供了丰富的协议。

Iterable<Foo> fooIterable;
List<Bar> barList = Iterate.collect(fooIterable, Foo::bar, new ArrayList<>());
Set<Bar> barSet = Iterate.collect(fooIterable, Foo::bar, new HashSet<>());

注意:我是 Eclipse Collections 的提交者。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接