在Java 8中,如何使用lambda将Map <K,V>转换为另一个Map <K,V>?


140

我刚刚开始研究Java 8,并尝试了lambda,我认为我想尝试重写最近写的一件非常简单的东西。我需要将字符串映射到列映射转换为另一个字符串映射到列映射,其中新映射中的列是第一个映射中列的防御性副本。列具有复制构造函数。到目前为止,我最接近的是:

    Map<String, Column> newColumnMap= new HashMap<>();
    originalColumnMap.entrySet().stream().forEach(x -> newColumnMap.put(x.getKey(), new Column(x.getValue())));

但我敢肯定,必须有更好的方法来完成这项工作,对于您的建议我将不胜感激。

Answers:


222

您可以使用收集器

import java.util.*;
import java.util.stream.Collectors;

public class Defensive {

  public static void main(String[] args) {
    Map<String, Column> original = new HashMap<>();
    original.put("foo", new Column());
    original.put("bar", new Column());

    Map<String, Column> copy = original.entrySet()
        .stream()
        .collect(Collectors.toMap(Map.Entry::getKey,
                                  e -> new Column(e.getValue())));

    System.out.println(original);
    System.out.println(copy);
  }

  static class Column {
    public Column() {}
    public Column(Column c) {}
  }
}

6
我认为上面要注意的重要(且有点违反直觉)的事情是,转换是在收集器中进行的,不是在map()流操作中进行的
Brian Agnew,

24
Map<String, Integer> map = new HashMap<>();
map.put("test1", 1);
map.put("test2", 2);

Map<String, Integer> map2 = new HashMap<>();
map.forEach(map2::put);

System.out.println("map: " + map);
System.out.println("map2: " + map2);
// Output:
// map:  {test2=2, test1=1}
// map2: {test2=2, test1=1}

您可以使用该forEach方法执行所需的操作。

您在那做的是:

map.forEach(new BiConsumer<String, Integer>() {
    @Override
    public void accept(String s, Integer integer) {
        map2.put(s, integer);     
    }
});

我们可以简化为lambda:

map.forEach((s, integer) ->  map2.put(s, integer));

而且因为我们只是在调用现有方法,所以我们可以使用method reference,它为我们提供了:

map.forEach(map2::put);

8
我喜欢您如何确切解释这里幕后发生的事情,它使事情变得更加清晰。但是我认为这只是给我原始地图的直接副本,而不是给新地图提供了防御性价值的新地图。我是否正确理解您可以使用(map2 :: put)的原因是将相同的参数输入到lambda中,例如(s,integer)与放入put方法中一样?因此,要制作一份防御性副本,还是需要originalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));缩短它?
annesadleir 2014年

是的,你是。如果您只是传递相同的参数,则可以使用方法引用,但是在这种情况下,由于您将其new Column(column)作为参数,因此必须这样做。
2014年

我更喜欢这个答案,因为如果两个地图都已经提供给您,例如在会话更新中,它就可以工作。
David Stanley,

不确定要了解这种答案的重点。它将像这样从现有Map重写几乎所有Map实现的构造函数。
Kineolyan

13

无需将所有条目重新插入新映射的方法应该是最快的方法,因为它HashMap.clone也在内部执行重新哈希。

Map<String, Column> newColumnMap = originalColumnMap.clone();
newColumnMap.replaceAll((s, c) -> new Column(c));

这是一种非常易读且简洁的方法。看起来像HashMap.clone()一样Map<String,Column> newColumnMap = new HashMap<>(originalColumnMap);。我不知道它的封面是否有任何不同。
annesadleir 2014年

2
这种方法的优点是保持Map实现的行为,即如果Mapa EnumMap或a SortedMap,结果Map也将是。在SortedMap特殊情况下,Comparator可能会产生巨大的语义差异。哦,这同样适用于IdentityHashMap...
霍尔格

8

保持简单并使用Java 8:

 Map<String, AccountGroupMappingModel> mapAccountGroup=CustomerDAO.getAccountGroupMapping();
 Map<String, AccountGroupMappingModel> mapH2ToBydAccountGroups = 
              mapAccountGroup.entrySet().stream()
                         .collect(Collectors.toMap(e->e.getValue().getH2AccountGroup(),
                                                   e ->e.getValue())
                                  );

在这种情况下:map.forEach(map2 :: put); 很简单:)
ses

5

如果在项目中使用Guava(最低v11),则可以使用Maps :: transformValues

Map<String, Column> newColumnMap = Maps.transformValues(
  originalColumnMap,
  Column::new // equivalent to: x -> new Column(x) 
)

注意:此映射的值是延迟计算的。如果转换费用昂贵,您可以按照Guava文档中的建议将结果复制到新地图。

To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.

注意:根据文档,此操作会在originalColumnMap上返回一个懒惰的求值视图,因此每次访问该条目时都会重新评估函数Column :: new(当映射函数昂贵时可能不希望如此)
Erric

正确。如果转换成本很高,则可以按照文档中的建议将其复制到新地图的开销可能不错。To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.
安德烈·贝尔贡佐

是的,但是对于那些倾向于跳过阅读文档的人来说,在答案中添加一个脚注可能是值得的。
艾里克

@Erric很有道理,只是添加了。
安德烈·贝尔贡佐

3

这是另一种使您可以同时访问键和值的方式,以防您必须进行某种转换。

Map<String, Integer> pointsByName = new HashMap<>();
Map<String, Integer> maxPointsByName = new HashMap<>();

Map<String, Double> gradesByName = pointsByName.entrySet().stream()
        .map(entry -> new AbstractMap.SimpleImmutableEntry<>(
                entry.getKey(), ((double) entry.getValue() /
                        maxPointsByName.get(entry.getKey())) * 100d))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

1

如果您不介意使用第三方库,则我的cyclops-react库具有所有JDK Collection类型的扩展,包括Map。您可以直接使用map或bimap方法来转换Map。MapX可以从现有的Map构建,例如

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .map(c->new Column(c.getValue());

如果您还想更改密钥,则可以编写

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .bimap(this::newKey,c->new Column(c.getValue());

bimap可用于同时转换键和值。

随着MapX扩展Map,生成的地图也可以定义为

  Map<String, Column> y
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.