【问题标题】:java 8 compare 2 lists based on propertyjava 8根据属性比较2个列表
【发布时间】:2019-06-21 20:26:27
【问题描述】:

我必须比较两个列表(list1 到 list2)上的元素,当元素与 code 属性的元素匹配时, 我必须用 tobereplace 属性的值替换。

在 list1 我有

[{code:"1", name:"first name", tobereplace: ""} , {code:"2", name:"first name1", tobereplace: ""}, 
{code:"3", name:"first name2", tobereplace: ""}, {code:"4", name:"first name3", tobereplace: ""}]

在 List2 中我有:

[{code:"1", name:"first name", tobereplace: ""} , {code:"2", name:"first name1", tobereplace: "" }, 
{code:"3", name:"first name2", tobereplace: ""}, {code:"4", name:"first name3", tobereplace: "this should come in list1"}]

理想情况下,List1 应该具有 List2 的值; 我们如何使用 Java8 流来实现这一点

【问题讨论】:

  • 您基于所有属性还是仅基于特定属性?
  • 特定的一个属性
  • 哪一个tobereplace
  • 是的,要替换属性
  • 你能告诉我们预期的结果吗?

标签: java list java-8 compare java-stream


【解决方案1】:

假设类名为Foo,并且要更改的字段是String valueToReplace,带有getter/setter,您可以这样使用listOne.replaceAll()

list1.replaceAll(one ->  list2.stream()
                              .filter(other -> other.getCode().equals(one.getCode())
                              .findAny()
                              .map(Foo::getValueToReplace)
                              .ifPresent( newValue -> one.setValueToReplace(newValue));
                        return one;
                )

这个想法是对于list1 的每个元素,您将其valueToReplace 字段替换为list2 的第一个匹配项的值。否则你什么都不做。
这段代码效率不高,但对于小列表来说它是完美的。
对于更大的列表,非常欢迎使用Map 存储code/valueToReplace

// supposing that the code are unique in each list
Map<Integer, String>  mapOfValueToReplaceByCode =
    list2.stream()
         .collect(toMap(Foo::getCode, Foo::getValueToReplace));

list1.replaceAll(one -> {
                 String newValue = mapOfValueToReplaceByCode.get(one.getCode());
                 if (newValue != null){ 
                     one.setValueToReplace(newValue);
                 }
                  return one;
                )

【讨论】:

  • 如果list2 包含重复的密钥,您将得到ava.lang.IllegalStateException: Duplicate key %s (attempted merging values %s and %s)
  • 同意。这就是我在此操作之前添加此注释的原因:“假设代码在每个列表中都是唯一的”。如果代码在列表中不是唯一的,处理重复项将使核心更加复杂,如果代码在列表中不是唯一的,则根据要求,我认为某处存在误解:为什么用找到的第一个元素而不是找到的第二个元素替换?
  • 对我来说,要求是解释所有情况,包括不预先聚合
【解决方案2】:

只需将替换值保存在映射中(以代码为键),然后遍历 list1 以在必要时进行修改。

Map<String, String> replaceValues = list2.stream()
    .collect(Collectors.toMap(x -> x.code, x -> x.tobereplace));
list1.stream
    .filter(x -> replaceValues.containsKey(x.code))
    .forEach(x -> x.tobereplace = replaceValues.get(x.code));

编辑

正如 josejuan 在 cmets 中指出的那样,如果 list2 包含重复值,Collectors.toMap 将引发异常。 OP 并没有真正指定在这种情况下要做什么,但解决方案是在 Collectors.toMap 中使用合并函数。

这将使用任何给定代码遇到的第一个元素:

Map<String, String> replaceValues = list2.stream()
    .collect(Collectors.toMap(x -> x.code, x -> x.tobereplace, (x1, x2) -> x1));

合并策略可以是任何东西,例如使用具有非空值的第一个元素,例如

如果已知列表没有重复项,请使用 Set 而不是 List。它会让阅读代码的人更清楚,并帮助您避免不必要的检查。

【讨论】:

  • 最好使用 getter 和 setter 而不是公共变量。除此之外,这是目前最好的解决方案。
  • 我同意,我只是避免冗长以使要点更清楚。
  • 说实话,我仍然在 99% 的代码中使用 getter 和 setter,即使只是因为它是一种约定。但是很多时候,getter 和 setter 都没有逻辑,我想知道在这些情况下是否真的值得。我说的主要是完全没有行为的数据容器“类结构”类。
  • 如果list2 包含重复的密钥,您将得到ava.lang.IllegalStateException: Duplicate key %s (attempted merging values %s and %s)
  • @josejuan,我刚刚编辑了问题以展示如何添加合并功能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-16
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多