Java8中如何合并两个map?

如何处理Map含有重复元素的情况?

1. 初始化

我们定义两个map实例

private static Map<String, People> map1 = new HashMap<>();
private static Map<String, People> map2 = new HashMap<>();

People类

class People {
    private Long id;
    private String name;

    public People(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    ......
    }
然后往map中存入一些数据
static {
        People people1 = new People(1L, "Henry");
        map1.put(people1.getName(), people1);

        People people2 = new People(2L, "Annie");
        map1.put(people2.getName(), people2);

        People people3 = new People(3L, "John");
        map1.put(people3.getName(), people3);

        People people4 = new People(4L, "George");
        map2.put(people4.getName(), people4);

        People people5 = new People(5L, "Henry");
        map2.put(people5.getName(), people5);
    }

特别需要注意的是people1 和 people5 在map中有完全相同的key(name)。

2. Map.merge()

Java8为 java.util.Map接口新增了merge()函数。

 merge()  函数的作用是: 如果给定的key之前没设置value 或者value为null, 则将给定的value关联到这个key上.

否则,通过给定的remaping函数计算的结果来替换其value。如果remapping函数的计算结果为null,将解除此结果。

// 通过拷贝map1中的元素来构造一个新的HashMap
Map<String, People> map3 = new HashMap<>(map1);
// 引入merge函数和合并规则 map3.merge(key, value, (v1, v2) -> new People(v1.getId(), v2.getName())
// map2进行迭代将其元素合并到map3中
map2.forEach(
        (key, value) -> map3.merge(key, value, (v1, v2) -> new People(v1.getId(), v2.getName())));
printMap("map3", map3);
/**
 * map3:
 * John:People{id=3, name='John'}
 * Annie:People{id=2, name='Annie'}
 * George:People{id=4, name='George'}
 * Henry:People{id=1, name='Henry'}
 */

最终,通过结果可以看出,实现了两个map的合并,对重复的key也合并为同一个元素。

注意最后一个People的id来自map1而name来自map2.

原因是我们的merge函数的定义:

(v1, v2) -> new People(v1.getId(), v2.getName())

3. Stream.concat()

Java8的Stream API 也为解决该问题提供了较好的解决方案。

首先需要将两个map合为一个Stream。

Stream combined = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream());

我们需要将entry sets作为参数,然后利用Collectors.toMap():将结果放到新的map中。

try {
     Map<String, People> map4 = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream()).collect(
             Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
     printMap("map4", map4);
 } catch (IllegalStateException e) {
     System.out.println("Error: " + e);
 }

该方法可以实现map的合并,但是有重复key会报IllegalStateException异常。

为了解决这个问题,我们需要加入lambda表达式merger作为第三个参数

(value1, value2) -> new Peopel(value2.getId(), value1.getName())

当检测到有重复Key时就会用到该lambda表达式。

Map<String, People> map5 = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (value1, value2) -> new People(value2.getId(), value1.getName())));
printMap("map5", map5);
/**
 * map5:
 * George:People{id=4, name='George'}
 * John:People{id=3, name='John'}
 * Annie:People{id=2, name='Annie'}
 * Henry:People{id=5, name='Henry'}
 */

从结果可以看出重复的key “Henry”将合并为一个新的键值对,id取自map2,name取自map1。

4. Stream.of()

通过Stream.of()方法不需要借助其他stream就可以实现map的合并。

Map<String, People> map6 = Stream.of(map1, map2)
        .flatMap(map -> map.entrySet().stream())
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (v1, v2) -> new People(v1.getId(), v2.getName())));
printMap("map6", map6);
/**
 * map6:
 * George:People{id=4, name='George'}
 * John:People{id=3, name='John'}
 * Annie:People{id=2, name='Annie'}
 * Henry:People{id=1, name='Henry'}
 */

首先将map1和map2的元素合并为同一个流,然后再转成map。通过使用v1的id和v2的name来解决重复key的问题。

5. Simple Streaming

我们还可以借助stream的管道操作来实现map合并。

Map<String, People> map7 = map2.entrySet()
        .stream()
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (v1, v2) -> new People(v1.getId(), v2.getName()),
                () -> new HashMap<>(map1)));
printMap("map7", map7);
/**
 * map7:
 * John:People{id=3, name='John'}
 * Annie:People{id=2, name='Annie'}
 * George:People{id=4, name='George'}
 * Henry:People{id=1, name='Henry'}
 */


package learn;


import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class People {
    private Long id;
    private String name;

    public People(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "People{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

public class TestMergeMap {
    private static Map<String, People> map1 = new HashMap<>();
    private static Map<String, People> map2 = new HashMap<>();

    static {
        People people1 = new People(1L, "Henry");
        map1.put(people1.getName(), people1);

        People people2 = new People(2L, "Annie");
        map1.put(people2.getName(), people2);

        People people3 = new People(3L, "John");
        map1.put(people3.getName(), people3);

        People people4 = new People(4L, "George");
        map2.put(people4.getName(), people4);

        People people5 = new People(5L, "Henry");
        map2.put(people5.getName(), people5);
    }

    public static void main(String[] args) {
        // 通过拷贝map1中的元素来构造一个新的HashMap
        Map<String, People> map3 = new HashMap<>(map1);
        // 引入merge函数和合并规则 map3.merge(key, value, (v1, v2) -> new People(v1.getId(), v2.getName())
        // map2进行迭代将其元素合并到map3中
        map2.forEach(
                (key, value) -> map3.merge(key, value, (v1, v2) -> new People(v1.getId(), v2.getName())));
        printMap("map3", map3);
        /**
         * map3:
         * John:People{id=3, name='John'}
         * Annie:People{id=2, name='Annie'}
         * George:People{id=4, name='George'}
         * Henry:People{id=1, name='Henry'}
         */
        try {
            Map<String, People> map4 = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream()).collect(
                    Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
            printMap("map4", map4);
        } catch (IllegalStateException e) {
            System.out.println("Error: " + e);
        }
        Map<String, People> map5 = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (value1, value2) -> new People(value2.getId(), value1.getName())));
        printMap("map5", map5);
        /**
         * map5:
         * George:People{id=4, name='George'}
         * John:People{id=3, name='John'}
         * Annie:People{id=2, name='Annie'}
         * Henry:People{id=5, name='Henry'}
         */
        Map<String, People> map6 = Stream.of(map1, map2)
                .flatMap(map -> map.entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (v1, v2) -> new People(v1.getId(), v2.getName())));
        printMap("map6", map6);
        /**
         * map6:
         * George:People{id=4, name='George'}
         * John:People{id=3, name='John'}
         * Annie:People{id=2, name='Annie'}
         * Henry:People{id=1, name='Henry'}
         */
        Map<String, People> map7 = map2.entrySet()
                .stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (v1, v2) -> new People(v1.getId(), v2.getName()),
                        () -> new HashMap<>(map1)));
        printMap("map7", map7);
        /**
         * map7:
         * John:People{id=3, name='John'}
         * Annie:People{id=2, name='Annie'}
         * George:People{id=4, name='George'}
         * Henry:People{id=1, name='Henry'}
         */
    }

    private static void printMap(String topic, Map<String, People> map) {
        System.out.println(topic + ":");
        map.forEach((key, value) -> {
            System.out.println(key + ":" + value);
        });
    }
}