Java 8中将List<T>转换为Map<K, V>

5

我希望将对象列表转换成Map,其中Map的键和值位于列表中的对象属性中。

以下是Java 7中进行此类转换的片段:

private Map<String, Child> getChildren(List<Family> families  ) {
        Map<String, Child> convertedMap = new HashMap<String, Child>();

        for (Family family : families) {
            convertedMap.put(family.getId(), family.getParent().getChild());
        }
        return convertedMap;
    }
2个回答

11

应该是类似于这样的东西...

Map<String, Child> m = families.stream()
    .collect(Collectors.toMap(Family::getId, f -> f.getParent().getChild()));

9

Jason给出了一个不错的答案 (+1),但我应该指出它与OP的Java 7代码具有不同的语义。问题涉及如果输入列表中有两个具有重复ID的家庭实例的行为。也许它们是保证唯一的,在这种情况下没有区别。如果有重复,然而,在OP的原始代码中,列表中后面的Family将覆盖列表中较早的具有相同ID的Family的映射条目。

使用Jason的代码(稍作修改):

Map<String, Child> getChildren(List<Family> families) {
    return families.stream()
        .collect(Collectors.toMap(Family::getId, f -> f.getParent().getChild()));
}
Collectors.toMap 操作会抛出 IllegalStateException 如果有任何重复的键。这有点不愉快,但至少它会通知您存在重复项,而不是悄悄地丢失数据。对于 Collectors.toMap(keyMapper, valueMapper) 的规则是,您需要确保键映射函数为流的每个元素返回唯一的键。
关于此问题,您需要做什么(如果需要)取决于问题域。一个可能性是使用三个参数的版本:Collectors.toMap(keyMapper, valueMapper, mergeFunction)。这指定了一个额外的函数,在重复的情况下调用它。如果您想让后面的条目覆盖先前的条目(与原始的 Java 7 代码匹配),则可以这样做:
Map<String, Child> getChildren(List<Family> families) {
    return families.stream()
        .collect(Collectors.toMap(Family::getId, f -> f.getParent().getChild(),
                                                 (child1, child2) -> child2));
}

另一种方法是为每个家庭建立一个孩子列表,而不是只有一个孩子。您可以编写一个更复杂的合并函数,为第一个孩子创建一个列表,并将第二个及其后续孩子附加到此列表中。这很常见,以至于有一个特殊的groupingBy收集器可以自动完成此操作。单独使用它会产生按ID分组的家庭列表。我们不想要家庭列表,而是想要孩子列表,因此我们添加了下游映射操作,从家庭映射到孩子,然后将孩子收集到列表中。代码如下:
Map<String, List<Child>> getChildren(List<Family> families) {
    return families.stream()
        .collect(Collectors.groupingBy(Family::getId,
                    Collectors.mapping(f -> f.getParent().getChild(),
                        Collectors.toList())));
}

请注意,返回类型已从 Map<String, Child> 更改为 Map<String, List<Child>>

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