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

问题描述 投票:0回答:3

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

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

    //Setters and Getters
}

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

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

但是当我尝试合并两个地图时,我得到的是空地图。 实际上,我需要一张包含三个键(印度、英国、美国)的地图,以及它们的值,这些值是从多个地图中列出的,以便与键合并。

java lambda collections hashmap java-stream
3个回答
2
投票

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

MapAdder.clearStudentMap();

您正在清除

studentMap1
studentMap2

当你这样做时:

studentMap1 = MapAdder.getStudentMap();

您将获得存储学生

Map
的内存引用。当您在该地图上调用
clear
方法时

studentMap.clear();

您将清除存储在同一内存引用上的所有

Map
条目。换句话说,下面的语句

studentMap1 = MapAdder.getStudentMap();

不会创建学生

Map
的副本,而是仅将对该 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; }
        ));

您还需要添加用于处理重复键的策略(

mergeFunction
方法的
Collectors.toMap
参数)。如果出现重复的键,我们将映射值添加到 left 键的列表中。

顺便说一句,在我看来,删除了其中一些辅助方法,它们混淆了代码,并通过将

addToMap
本身作为参数传递,使 Map 方法更加
通用
,以便您可以在不同的映射器中重用该方法,即:

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

0
投票

创建

HashMap
实例时,您可以重写其
put
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]

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

studentMap = new HashMap<>(studentMap);

另请参阅:“contains”方法不适用于 ArrayList,还有其他方法吗?


0
投票

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

但是有一种比使用

MapAdder
类更简单的方法来添加值。 请记住,
country
也是学生班级的一部分。 因此,只需提取它并使用流创建地图即可。

现在创建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 类中。 但我们假设密钥独立于类。 然后你可以使用你的

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

具有 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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.