【问题标题】:Lambda Collect elements created inside an consumer methodLambda 收集在消费者方法中创建的元素
【发布时间】:2015-09-17 13:55:36
【问题描述】:

我正在从丑陋的嵌套 for 循环转变为在 java 中设计精美的 lambda 表达式。

这是我的实际代码

for (String foo : foos) {
    for (Bar bar : bars) {    
        if (bar.getFoo().equals(foo)) {
            FooBar fooBar = new FooBar();                           
            fooBar.setBar(bar);
            listOfFooBar.add(fooBar);
            break;
        }
    }
}

我的实际 lambda 代码替换上面的代码

foos.forEach(i -> bars.stream().filter(p -> p.getFoo().equals(i)).findFirst().ifPresent(p -> {
        FooBar s = new FooBar();
        fooBar.setBar(bar);
        listOfFooBar.add(fooBar);
    }));

我的问题是,有没有办法用某种 collect() 方法填充 listOfFooBar?

类似listOfFooBar = foos.forEach(.....).collect(Collectors.toList());

一个事实是,bars 将始终包含每个 foo,foos 基本上是 bars 的一小部分。

如果有更好的方法(在性能或优雅方面)来做那个 lambda,请分享。

【问题讨论】:

  • 我认为应该是bar.getFoo()而不是bar.getBar(),对吗?
  • @FedericoPeraltaSchaffner 是的,这确实更有意义:)
  • 为什么每个人都想先用forEach 做所有事情? Stream 有更多的方法。
  • @Holger 我称之为“for-each-itis”,我相信这是或将是一种常见的代码气味。我认为原因是forEach 可能是不熟悉 FP 结构的程序员最容易理解的结构。由于命令式程序有很多循环,最简单的第一步是使用forEach 将它们转换为流。不幸的是,它并不容易引向任何地方,然后人们就会陷入困境。
  • 顺便说一句,这并不是对 OP 的批评。他尝试了一些东西,也许是感觉有更好的方法,然后来到StackOverflow提问和学习。 +1

标签: java lambda java-8 java-stream


【解决方案1】:

由于每个 Foo 只有一个 Bar,您可以先创建一个将 Foos 链接到 Bars 的地图:

Map<String, Bar> barsByFoo = bars.stream().collect(toMap(Bar::getFoo, b -> b));

如果你的 bar 比 foos 多很多,你可以过滤:

Map<String, Bar> barsByFoo = bars.stream()
                                 .filter(b -> foos.contains(b.getFoo()))
                                 .collect(toMap(Bar::getFoo, b -> b));

然后可以编写嵌套的 for 循环:

List<FooBar> listOfFooBar = foos.stream()
        .map(barsByFoo::get)
        .filter(Objects::nonNull)
        .map(FooBar::new)
        .collect(toList());

这假设有一个FooBar(Bar) 构造函数。

或者您可以从另一边解决问题并使用(我认为)等效算法(在这种情况下,您可能会从使用Set&lt;Foo&gt; 中受益):

List<FooBar> listOfFooBar = bars.stream()
        .filter(bar -> foos.contains(bar.getFoo()))
        .map(FooBar::new)
        .collect(toList());

无论哪种方式,从初始循环中退出通常会有所帮助,因为不同的算法/方法通常有利于干净的 lambda 解决方案。

【讨论】:

  • 是的,每个 Foo 只有一个 Bar。我正在测试你的代码
  • @JohnnyWiller Collectors.toMap 或使用import static java.util.stream.Collectors.toMap;
  • 也许一个重要的事实是 bar 将始终包含每个 foo。实际上我想创建一些 FooBar 的实例,但我想创建唯一匹配 foos 的实例。我喜欢你的第四段代码,我只担心foos.contains,也许我可以改进它。
  • @JohnnyWiller 如果foos 是一组 foo 它不会比您的嵌套循环慢 - 实际上可能更快。如果它是一个 foo 列表,那么只需使用 Set&lt;String&gt; fooSet = new HashSet&lt;&gt; (foos);,然后使用 fooSet::contains。除非 bar 列表比 foos 列表大得多。
  • 我最后的方法是你的最后一段代码。谢谢!
【解决方案2】:

如果你想走完整个九码:

List<FooBar> listOfFooBar = foos.stream()
  .flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).findFirst()
                    .map(Stream::of).orElse(Stream.empty()))
  .map(bar -> {
                FooBar fooBar = new FooBar();
                fooBar.setBar(bar);
                return fooBar;
              })
  .collect(Collectors.toList());

如果您有一个接受Bar 的FooBar 构造函数,那么您可以节省一些行并编写

.map(FooBar::new)

你可以在 Java 9 中编写 FWIW

.findFirst().stream()

假设有一个合适的构造函数,那么它将缩短为

List<FooBar> listOfFooBar = foos.stream()
  .flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).findFirst().stream()))
  .map(FooBar::new)
  .collect(Collectors.toList());

编辑: 使用@Misha 的建议,您可以进一步缩短它:

List<FooBar> listOfFooBar = foos.stream()
  .flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).limit(1)))
  .map(FooBar::new)
  .collect(Collectors.toList());

【讨论】:

  • 您可以将.findFirst().map(Stream::of).orElse(Stream.empty())替换为.limit(1)
【解决方案3】:

如果FooBar 有一个接受Bar 作为参数的构造函数:

public class FooBar {

    public FooBar(Bar bar) {
        // do something with bar, assign it, etc
    }
}

那么,你可以这样做:

List<FooBar> fooBars = foos.stream()
    .map(foo -> bars.stream()
        .filter(bar -> bar.getFoo().equals(foo))
        .findFirst()
        .map(FooBar::new))
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

这会流式传输您的foos,并且对于每个foo,它会流式传输您的bars,直到找到与当前foo 匹配的第一个。如果实际找到foo,则从内部流的当前bar 创建一个新的FooBar。这给我们留下了Optional&lt;FooBar&gt; 的流,然后对其进行过滤以仅保留非空选项。然后,将可选项转换为它们包含的值(即上一步创建的FooBars),最后将这些FooBars 收集到List&lt;FooBar&gt;。

编辑: 那是我的第一次尝试。使用@zeroflagL 的方法要好得多:

List<FooBar> fooBars = foos.stream()
    .flatMap(foo -> bars.stream()
        .filter(bar -> bar.getFoo().equals(foo))
        .findFirst()
        .map(Stream::of).orElse(Stream.empty()))
    .map(FooBar::new)
    .collect(Collectors.toList());

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多