Java 流 (forEach) 从一个列表中获取属性并应用于另一个

IVR*_*ger 0 java foreach java-stream

我有一个简单的类:

public class Thing {

  Long id;
  String color;
  String size;

}
Run Code Online (Sandbox Code Playgroud)

我有两个List<Thing>看起来像这样的对象:

List<Thing> colors = [{2, red}, {3, blue}];
List<Thing> sizes = [{2, small}, {3, large}];
Run Code Online (Sandbox Code Playgroud)

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

List<Thing> things = [{2, red, small}, {3, blue, large}]
Run Code Online (Sandbox Code Playgroud)

rzw*_*oot 5

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

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

在前一种情况下(这不是关于“相结合的红色和小的,因为它们都具有ID 2,它是关于结合2,并red从第一个元素colorssmall从第一个元素sizes,无论IDS),你可以找到另一个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());
Run Code Online (Sandbox Code Playgroud)

它看起来很丑,如果列表不是随机访问,它的性能非常糟糕,在我几乎可以理解的所有方面,与仅制作 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());
}
Run Code Online (Sandbox Code Playgroud)

如果这是你想要的方式,你绝对不应该首先使用流。

不,这是关于ID。

在这种情况下,Streams 看起来很像您使用发现的非常闪亮的花式锤子在三明治上涂抹果酱:Streams 只是......在这里没有意义。在所有。你可以尝试一些 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());
Run Code Online (Sandbox Code Playgroud)

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