【问题标题】:Java streams (forEach) to take attributes from one list and apply to anotherJava 流 (forEach) 从一个列表中获取属性并应用于另一个列表
【发布时间】:2020-10-20 00:18:59
【问题描述】:

我有一个简单的课程:

public class Thing {

  Long id;
  String color;
  String size;

}

我有两个 List<Thing> 对象,如下所示:

List<Thing> colors = [{2, red}, {3, blue}];
List<Thing> sizes = [{2, small}, {3, large}];

使用 Java 流/forEach,我如何将一个“流”到另一个中以到达:

List<Thing> things = [{2, red, small}, {3, blue, large}]

【问题讨论】:

标签: java foreach java-stream


【解决方案1】:

这个概念(将 2 个长度相等的流混合在一起,这样您最终会从每个流中获取一个元素,都传递给一个函数,然后将该组合映射到其他东西)称为 zipping

Java 的流类不包含对此操作的任何支持。你在这里拥有的东西也不是你可以有意义地压缩在一起的东西,除非你能以某种方式保证两者都会以完全相同的顺序发生这个“id”概念(即,如果你的sizes[{3, large}, {2, small}] 而不是,我们是不再谈论 zip 操作)。

在前一种情况下(这不是关于'组合红色和小,因为它们都有 id 2,而是关于组合 colors 中第一个元素的 2red 与来自 small sizes 中的第一个元素,无论 id 是什么),你可以从另一个库中找到 zip 支持,或者破解它:

if (colors.size() != sizes.size()) throw new IllegalStateException();
IntStream.range(0, colors.size())
.mapToObj(i -> new Thing(colors.get(i).getId(), colors.get(i).getColor(), sizes.get(i).getSize()))
.collect(Collectors.toList());

它看起来很难看,如果列表不是随机访问,它的性能会非常糟糕,在几乎所有方面我都可以理解,与只创建 2 个迭代器并并行迭代它们相比,这真是令人失望:

if (colors.size() != sizes.size()) throw new IllegalStateException();
var itColors = colors.iterator();
var itSizes = sizes.iterator();
var out = new ArrayList<Thing>();
while (itColors.hasNext()) {
    Thing a = itColors.next();
    out.add(new Thing(a.getId(), a.getColor(), itSizes.next().getSize());
}

如果你想以这种方式处理它,你绝对不应该首先使用流。

不,是关于 ID。

在这种情况下,溪流看起来很像你用一把非常闪亮的花哨的锤子在三明治上涂抹果酱:溪流只是……在这里毫无意义。完全没有。你可以尝试一些 hacky 怪人的东西,好吧,实现这一点并使用流,但它会是片状代码:它看起来很奇怪,是非惯用的,包含大量警告(假设,如果不是真的,意味着代码无法正确完成工作,或者在某些情况下具有极差的性能特征),并且通常会变得更糟。

解决方案是地图:

var out = new HashMap<Long, Thing>();
for (Thing c : colors) out.put(c.getId(), c);
for (Thing s : sizes) out.merge(c.getId(), s,
   (a, b) -> new Thing(a.getId(), a.getColor(), b.getSize());

看看它有多干净,它还可以处理输入之间的任何不匹配(大小包含 id 87,但颜色不包含)。

【讨论】:

  • "...流只是...在这里毫无意义。" - 对。我的假设是 Streams 解决了所有这些奇怪的数据结构丑陋,显然他们没有。这涵盖了我坚持做得很好的事情。
【解决方案2】:

我会创建一个从 idThingMap&lt;Long, Thing&gt; 并首先放置带有颜色的东西,然后我会将大小设置为地图上的东西,前提是它们存在:

Map<Long, Thing> map = new LinkedHashMap<>();

colors.forEach(t -> map.put(
    t.getId(), 
    new Thing(t.getId(), t.getColor(), null))); // assumes constructor from attributes

sizes.forEach(t -> {
    Thing thing = map.get(t.getId());
    if (thing != null) thing.setSize(t.getSize());
});

这使得原来的Things 保持不变。

【讨论】:

    猜你喜欢
    • 2021-04-29
    • 2011-10-13
    • 2011-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-05
    相关资源
    最近更新 更多