如何将多个映射与多个相同的键和列表组合为值?

How to combine Multiple Maps with multiple same keys and lists as values?

我是 Java 的新手,我正在尝试合并多个以字符串作为键并以列表作为值的地图以生成新地图。

public class Student {
    private String name;
    private String country;

    //Setters and Getters
}

现在我有一个实用程序 class 可以根据学生所在的国家/地区将学生添加到列表中。

public class MapAdder {
    static Map<String, List<Student>> studentMap =
            new LinkedHashMap<String, List<Student>>();

    public static void addToMap(String key, Student student) {
        studentMap.computeIfAbsent(key,
                k -> new LinkedList<Student>()).add(student);
    }

    public static Map<String, List<Student>> getStudentMap() {
        return studentMap;
    }

    public static void clearStudentMap() {
        studentMap.clear();
    }
}

主要方法

public static void main(String[] args) {
    Map<String, List<Student>> studentMap1;
    Map<String, List<Student>> studentMap2;
    Map<String, List<Student>> studentMap3;

    MapAdder.addToMap("India", new Student("Mounish", "India"));
    MapAdder.addToMap("USA", new Student("Zen", "USA"));
    MapAdder.addToMap("India", new Student("Ram", "India"));
    MapAdder.addToMap("USA", new Student("Ronon", "USA"));
    MapAdder.addToMap("UK", new Student("Tony", "UK"));

    studentMap1 = MapAdder.getStudentMap();
    MapAdder.clearStudentMap();

    MapAdder.addToMap("India", new Student("Rivar", "India"));
    MapAdder.addToMap("UK", new Student("Loki", "UK"));
    MapAdder.addToMap("UK", new Student("Imran", "UK"));
    MapAdder.addToMap("USA", new Student("ryan", "USA"));

    studentMap2 = MapAdder.getStudentMap();
    MapAdder.clearStudentMap();

    Map<String, List<Student>> map3 = Stream.of(studentMap1, studentMap2)
            .flatMap(map -> map.entrySet().stream())
            .collect(Collectors.toMap(
                    Entry::getKey,
                    Entry::getValue
            ));
}

但是当我尝试合并两张地图时,我得到的是空地图。 实际上,我需要一个包含三个键(印度、英国、美国)的地图以及它们的值,这些值来自多个要合并的地图 w.r.t 个键。

首先,从您的代码中删除以下调用:

MapAdder.clearStudentMap();

您正在清除 studentMap1studentMap2

当你这样做时:

studentMap1 = MapAdder.getStudentMap();

你得到存储学生 Map 的内存引用。当您在该地图上调用 clear 方法时

studentMap.clear();

您将清除存储在同一内存引用中的所有 Map 条目。也就是说,下面的语句

studentMap1 = MapAdder.getStudentMap();

不会创建学生 Map 的副本,而只是在变量 studentMap1 上保存对该映射的内存引用。

你的Stream方法差不多对了,改成:

Map<String, List<Student>> map3 = Stream.of(studentMap1, studentMap2)
        .flatMap(map -> map.entrySet().stream())
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> new ArrayList<>(e.getValue()),
                (left, right) -> { left.addAll(right); return left; }
        ));

您还需要添加用于处理重复键的策略(Collectors.toMap方法的mergeFunction参数) .在重复键的情况下,我们将 Map 值添加到 left 键的列表中。

顺便说一句,删除一些辅助方法 IMO,它们混淆了代码,并通过将 Map 本身作为参数传递,使 addToMap 方法更 通用 ,这样您就可以在不同的映射器中重用该方法,即:

public class MapAdder {
    public static void addToMap(Map<String, List<Student>> studentMap,
                                String key, Student student) {
        studentMap.computeIfAbsent(key,
                k -> new LinkedList<Student>()).add(student);
    }

    public static void main(String[] args) {
        Map<String, List<Student>> studentMap1 = new LinkedHashMap<>();
        Map<String, List<Student>> studentMap2 = new LinkedHashMap<>();
        Map<String, List<Student>> studentMap3;

        MapAdder.addToMap(studentMap1, "India", new Student("Mounish", "India"));
        MapAdder.addToMap(studentMap1, "USA", new Student("Zen", "USA"));
        MapAdder.addToMap(studentMap1, "India", new Student("Ram", "India"));
        MapAdder.addToMap(studentMap1, "USA", new Student("Ronon", "USA"));
        MapAdder.addToMap(studentMap1, "UK", new Student("Tony", "UK"));

        MapAdder.addToMap(studentMap2, "India", new Student("Rivar", "India"));
        MapAdder.addToMap(studentMap2, "UK", new Student("Loki", "UK"));
        MapAdder.addToMap(studentMap2, "UK", new Student("Imran", "UK"));
        MapAdder.addToMap(studentMap2, "USA", new Student("ryan", "USA"));

        Map<String, List<Student>> map3 = Stream.of(studentMap1, studentMap2)
                .flatMap(map -> map.entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        e -> new ArrayList<>(e.getValue()),
                        (left, right) -> { left.addAll(right); return left; }
                ));
    }
}

主要问题是您不断清除共享列表。需要创建独立列表。

但是有一种比使用 MapAdder class 更简单的方法来添加值。请记住 country 也是学生 class 的一部分。所以只需提取它并使用流创建地图。

现在创建studentMap1

List<Student> list1 = List.of(
new Student("Mounish", "India"),
new Student("Zen", "USA"),
new Student("Ram", "India"),
new Student("Ronon", "USA"),
new Student("Tony", "UK"));
Map<String, List<Student>> studentMap1 = 
     list1.stream().collect(Collectors.groupingBy(Student::getCountry));

studentMap1.entrySet().forEach(System.out::println);

打印

USA=[{Zen,  USA}, {Ronon,  USA}]
UK=[{Tony,  UK}]
India=[{Mounish,  India}, {Ram,  India}]

现在创建studentMap2

List<Student> list2 = List.of(
new Student("Rivar", "India"),
new Student("Loki", "UK"),
new Student("Imran", "UK"),
new Student("ryan", "USA"));
Map<String, List<Student>> studentMap2 = 
   list2.stream().collect(Collectors.groupingBy(Student::getCountry));

studentMap2.entrySet().forEach(System.out::println);

版画

USA=[{ryan,  USA}]
UK=[{Loki,  UK}, {Imran,  UK}]
India=[{Rivar,  India}]

现在您已经有了地图,您可以用同样的方式创建组合地图。只需使用每个映射的值,然后将它们流式传输以获取学生实例。

Map<String, List<Student>> map3 = Stream.of(studentMap1,studentMap2)
        .map(Map::values)               // values which is a collection of lists
        .flatMap(Collection::stream)    // flat map the two collections
        .flatMap(Collection::stream)    // flat map the lists to just
                                        // a stream of students
        .collect(Collectors.groupingBy(Student::getCountry));

map3.entrySet().forEach(System.out::println);

版画

USA=[{Zen,  USA}, {Ronon,  USA}, {ryan,  USA}]
UK=[{Tony,  UK}, {Loki,  UK}, {Imran,  UK}]
India=[{Mounish,  India}, {Ram,  India}, {Rivar,  India}]

您很幸运,Map 键作为 Student 的一部分包含在内 class。但是我们假设密钥独立于 class。然后你可以使用你的 mapAdder 来构建原始地图。最终的地图可以使用下面的 merge 函数来创建,用于合并重复键。

Map<String, List<Student>> map4 =
   Stream.of(studentMap1, studentMap2)
        .flatMap(m -> m.entrySet().stream())
        .collect(Collectors.toMap(Entry::getKey,
            e -> new ArrayList<>(e.getValue),
           (lst1, lst2) -> {lst1.addAll(lst2); return lst1;}));

学生 class 具有 getter 和 setter 以及 toString

class Student {
    private String name;
    private String country;
    
    public Student(String name, String country) {
        this.name = name;
        this.country = country;
    }
    
    public String getName() {
        return name;
    }
    
    public String getCountry() {
        return country;
    }
    
    @Override
    public String toString() {
        return String.format("{%s,  %s}", name, country);
    }
}

创建 HashMap instance, you can override its put and putAll 方法时,它们不会替换现有值,而是附加它们,即合并相同键的值列表:

Map<String, List<Student>> studentMap = new HashMap<>() {
    @Override
    public List<Student> put(String key, List<Student> value) {
        if (this.containsKey(key)) {
            List<Student> val = this.get(key);
            val.addAll(value);
            return val;
        } else {
            return super.put(key, new ArrayList<>(value));
        }
    }

    @Override
    public void putAll(Map<? extends String, ? extends List<Student>> m) {
        Iterator<? extends Entry<? extends String, ? extends List<Student>>>
                iterator = m.entrySet().iterator();

        while (iterator.hasNext()) {
            Entry<? extends String, ? extends List<Test.Student>>
                    e = iterator.next();
            this.put(e.getKey(), e.getValue());
        }
    }
};
studentMap.put("India", List.of(new Student("Mounish", "India")));
studentMap.put("USA", List.of(new Student("Zen", "USA")));

studentMap.putAll(Map.of(
        "India", List.of(new Student("Ram", "India")),
        "USA", List.of(new Student("Ronon", "USA")),
        "UK", List.of(new Student("Tony", "UK"))));

studentMap.putAll(Map.of(
        "India", List.of(new Student("Rivar", "India")),
        "UK", List.of(new Student("Loki", "UK"))));

studentMap.putAll(Map.of(
        "UK", List.of(new Student("Imran", "UK")),
        "USA", List.of(new Student("ryan", "USA"))));
studentMap.forEach((k, v) -> System.out.println(k + "=" + v));
// USA=[Zen:USA, Ronon:USA, ryan:USA]
// UK=[Tony:UK, Loki:UK, Imran:UK]
// India=[Mounish:India, Ram:India, Rivar:India]

如果您不再需要此扩展功能,可以将其删除并return到常规地图:

studentMap = new HashMap<>(studentMap);

另请参阅:The 'contains' method does not work for ArrayList<int[]>, is there another way?