Java 8列表纳入映射

问题描述:

我想将对象列表转换为映射,其中映射的键和值位于列表中的对象的列表中。Java 8列表<T>纳入映射<K, V>

这里Java 7的片断这样convertation的:

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; 
    } 

应该是类似的东西...

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

杰森给了decent answer(+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>>