如何优雅地交集两个由两个映射构建的集合?

4
我们的对象具有“属性”,它们的当前状态表示为Map<String, Object>,其中键类似于属性名称。值可以具有不同的类型,我的当前任务仅涉及布尔属性。
除了当前状态之外,对象的“更新”也是通过这些映射组织的。
现在我必须防止当前为true的属性被禁用(变为false)。
使用流,这里可以工作:
Set<String> currentlyEnabled = currentObjectPropertiesMap.
            .entrySet()
            .stream()
            .filter(e -> Boolean.TRUE.equals(e.getValue()))
            .map(Entry::getKey)
            .collect(Collectors.toSet());

Set<String> goingDisabled = updatedObjectPropertiesMap
        .entrySet()
        .stream()
        .filter(e -> Boolean.FALSE.equals(e.getValue()))
        .map(Entry::getKey)
        .collect(Collectors.toSet());

currentlyEnabled.retainAll(goingDisabled);

if (currentlyEnabled.isEmpty()) {
    return;
} else {
  throw new SomeExceptionThatKnowsAllBadProperties(currentlyEnabled);
}

上面的代码首先获取所有属性中为true的集合,然后分别收集所有将变为false的属性。如果这两个集合的交集为空,则一切正常,否则出现错误。
上述方法可行,但我觉得有些笨重,并且不喜欢当前启用的集合被误用来计算交集。
有没有更加通俗易懂、符合惯用方式的流处理方法呢?

1
为什么不迭代currentObjectPropertiesMap.entrySet(),在值为 false 的条目上使用 continue,否则检查 updatedObjectPropertiesMap.hasKey() 是否为 true,如果该键的值为 false 则抛出异常? - SomethingSomething
3个回答

7
您可以选择所有值为true的键值对,然后通过键,检查“更新”映射中的值是否为false
Set<String> matches = currentObjectPropertiesMap
    .entrySet()
    .stream()
    .filter(e -> Boolean.TRUE.equals(e.getValue()))
    .map(Map.Entry::getKey)
    .filter(k -> Boolean.FALSE.equals(
        updatedObjectPropertiesMap.get(k)
    ))
    .collect(Collectors.toSet());

if(!matches.isEmpty()) throw ...

1
可以将这两个 filter 合并成一个,然后再使用 map。虽然差别不大。.filter(e -> Boolean.TRUE.equals(e.getValue()) && Boolean.FALSE.equals(updatedObjectPropertiesMap.get(e.getKey()))) .map(Map.Entry::getKey) - Naman
1
@Naman 我同意,但在我看来,流语句应该尽可能简单。我的经验法则是每个filter/map操作只有一个条件/语句(如果可能的话),但正如所说,这只是一种观点。 - Lino
2
为了提高效率,您可以检查哪个映射较小,并迭代较小的映射,例如,如果updatedObjectPropertiesMap较小,则使用以下代码:Set<String> matches = updatedObjectPropertiesMap.entrySet().stream().filter(e -> Boolean.FALSE.equals(e.getValue())).map(Map.Entry::getKey).filter(k -> Boolean.TRUE.equals(currentObjectPropertiesMap.get(k))).collect(Collectors.toSet());结果将是相同的。 - Holger

2

一种不包括显式集合交集的解决方案可能是:

最初的回答:

Set<String> violatingProperties = new HashSet<String>();
for (Entry<String, Object> entry : currentObjectPropertiesMap.entrySet()) {
    if (! (Boolean) entry.getValue()) {
        continue;
    }
    if (! updatedObjectPropertiesMap.hasKey(entry.getKey())) {
        continue;
    }
    if (! (Boolean) updatedObjectPropertiesMap.get(entry.getKey())) {
        violatingProperties.add(entry.getKey());
    }
}
if (violatingProperties.size() > 0) {
    throw ...
}

问题在于,我A)更喜欢流式解决方案,并且B)需要“收集”违反我的条件的所有属性名称。 - GhostCat
更新了代码,使得违反属性名称将被保存。 - SomethingSomething

0
尝试使用anyMatch
boolean anyMatch = currentXXXMap.entrySet()
    .stream()
    .anyMatch(e -> e.getValue() && !updatedXXXMap.getOrDefault(e.getKey(), true));

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