zoukankan      html  css  js  c++  java
  • Java 8中Collection转为Map的方法

    Java 8中java.util.stream.Collectors提供了几个方法可用于把Collection转为Map结构,本文记录了个人对其中三个的理解。

    Method Return Type
    groupingBy Map<K, List<T>>
    partitioningBy Map<Boolean, List<T>>
    toMap Map<K,U>

    1. 环境

    Java: jdk1.8.0_144

    2. 特性说明

    Student.java

    public class Student {
        private String studentNo;
        private String name;
        private Boolean gender;
        private int age;
    
        public Student(String studentNo, String name, Boolean gender, int age) {
            this.studentNo = studentNo;
            this.name = name;
            this.gender = gender;
            this.age = age;
        }
    
        public String getStudentNo() {
            return studentNo;
        }
    
        public String getName() {
            return name;
        }
    
        public Boolean getGender() {
            return gender;
        }
    
        public int getAge() {
            return age;
        }
    
        @Override
        public String toString() {
            return String.format("Student [studentNo=%s, name=%s, gender=%s, age=%s]", studentNo, name, gender, age);
        }
    }
    
    

    fakeStudent()方法

    private List<Student> fakeStudent() {
        List<Student> students = new ArrayList<>();
        students.add(new Student("1", "name1", false, 2));
        students.add(new Student("2", "name2", false, 2));
        students.add(new Student("3", "name2", null, 2));
        students.add(new Student("4", "name4", true, 2));
        students.add(new Student(null, "name5", true, 2));
        return students;
    }
    

    2.1. Collectors.groupingBy

    public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }
    
    a) 按Function的返回值把集合分组,并以之为Key,对应的列表为Value,返回Map
    b) 若Key对应的列表为空时,返回的Map中将不包含该Key
    c) 若Function的返回值为Null,抛出NullPointerException
    @Test(expected = NullPointerException.class)
    public void shouldThrowNPEWhenGroupingByNullKey() {
        fakeStudent().stream().collect(Collectors.groupingBy(Student::getStudentNo));
    }
    

    2.2. Collectors.partitioningBy

    public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
        return partitioningBy(predicate, toList());
    }
    
    a) 按Predicate的返回值把集合分为两组,符合条件的列表以true为Key,不符合的列表以false为Key
    b) 若Predicate的返回值为Null,抛出NullPointerException
    @Test(expected = NullPointerException.class)
    public void shouldReturnMapWhenPartitioningByNullKey() {
        fakeStudent().stream().collect(Collectors.partitioningBy(Student::getGender));
    }
    

    2.3. Collectors.toMap

    public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }
    
    a) 以keyMapper的Function返回值为Key且以valueMapper的Function返回值为Value,形成Map
    b) 若Key为Null,依然可以正确返回
    @Test
    public void shouldReturnMapWhenToMapNullKey() {
        Map<String, Student> map = fakeStudent().stream()
                .collect(Collectors.toMap(Student::getStudentNo, Function.identity()));
        assertEquals("{null=Student [studentNo=null, name=name5, gender=true, age=2], "
                + "1=Student [studentNo=1, name=name1, gender=false, age=2], "
                + "2=Student [studentNo=2, name=name2, gender=false, age=2], "
                + "3=Student [studentNo=3, name=name2, gender=null, age=2], "
                + "4=Student [studentNo=4, name=name4, gender=true, age=2]}", map.toString());
    }
    
    c) 若Key值出现重复,默认抛出IllegalStateException
    @Test
    public void shouldThrowIllegalStateExceptionWhenToMapDuplicateKey() {
        Map<String, Student> map = null;
        try {
            map = fakeStudent().stream().collect(Collectors.toMap(Student::getName, Function.identity()));
        } catch (Exception e) {
            assertTrue(e instanceof IllegalStateException);
            assertEquals("Duplicate key Student [studentNo=2, name=name2, gender=false, age=2]", e.getMessage());
        }
        assertNull(map);
    }
    

    若需要避免Duplicate Key的问题,可以有两个选择

    • 确定toMap的冲突策略,例如指定前者
    @Test
    public void shouldReturnMapWhenToMapDuplicateKey() {
        Map<String, Student> map = fakeStudent().stream()
                .collect(Collectors.toMap(Student::getName, Function.identity(), (student1, student2) -> student1));
        assertEquals("{name5=Student [studentNo=null, name=name5, gender=true, age=2], "
                + "name4=Student [studentNo=4, name=name4, gender=true, age=2], "
                + "name2=Student [studentNo=2, name=name2, gender=false, age=2], "
                + "name1=Student [studentNo=1, name=name1, gender=false, age=2]}", map.toString());
    }
    
    • 放弃toMap方法,而利用collect
    @Test
    public void shouldReturnMapWhenCollectDuplicateKey() {
        Map<String, Student> map = fakeStudent().stream().collect(HashMap::new, (m, v) -> m.put(v.getName(), v),
                HashMap::putAll);
        assertEquals("{name5=Student [studentNo=null, name=name5, gender=true, age=2], "
                + "name4=Student [studentNo=4, name=name4, gender=true, age=2], "
                + "name2=Student [studentNo=3, name=name2, gender=null, age=2], "
                + "name1=Student [studentNo=1, name=name1, gender=false, age=2]}", map.toString());
    }
    
    d) 若Value为Null,则抛出NullPointerException
    @Test(expected = NullPointerException.class)
    public void shouldThrowNPEWhenToMapNullValue() {
        fakeStudent().stream().collect(Collectors.toMap(Student::getStudentNo, Student::getGender));
    }
    

    3. 结语

    • Collectors.groupingBy/Collectors.partitioningBy中心思想都是把原来集合以某种条件分组,分组条件不能为Null;只是Collectors.partitioningBy的分组条件是断言,且永远返回true/false对应的两组值,它们对应的Value可能是空列表,而Collectors.groupingBy的分组结果是空列表则会被抛弃
    @Test
    public void shouldReturnSameMapWhenGroupingByAndPartitioningBy() {
        List<Student> students = fakeStudent().stream().filter(student -> student.getGender() != null)
                .collect(Collectors.toList());
        Map<Boolean, List<Student>> groupingByMap = students.stream()
                .collect(Collectors.groupingBy(Student::getGender));
        Map<Boolean, List<Student>> partitioningByMap = students.stream()
                .collect(Collectors.partitioningBy(Student::getGender));
        assertEquals("{false=[Student [studentNo=1, name=name1, gender=false, age=2], "
                + "Student [studentNo=2, name=name2, gender=false, age=2]], "
                + "true=[Student [studentNo=4, name=name4, gender=true, age=2], "
                + "Student [studentNo=null, name=name5, gender=true, age=2]]}", groupingByMap.toString());
        assertEquals(groupingByMap.toString(), partitioningByMap.toString());
    }
    
    @Test
    public void shouldReturnDifferentMapWhenGroupingByAndPartitioningBy() {
        Function<Student, Boolean> function = student -> student.getAge() > 3;
        List<Student> students = fakeStudent();
        Map<Boolean, List<Student>> groupingByMap = students.stream().collect(Collectors.groupingBy(function));
        Map<Boolean, List<Student>> partitioningByMap = students.stream()
                .collect(Collectors.partitioningBy(function::apply));
        assertEquals("{false=[Student [studentNo=1, name=name1, gender=false, age=2], "
                + "Student [studentNo=2, name=name2, gender=false, age=2], "
                + "Student [studentNo=3, name=name2, gender=null, age=2], "
                + "Student [studentNo=4, name=name4, gender=true, age=2], "
                + "Student [studentNo=null, name=name5, gender=true, age=2]]}", groupingByMap.toString());
        assertEquals(
                "{false=[Student [studentNo=1, name=name1, gender=false, age=2], "
                        + "Student [studentNo=2, name=name2, gender=false, age=2], "
                        + "Student [studentNo=3, name=name2, gender=null, age=2], "
                        + "Student [studentNo=4, name=name4, gender=true, age=2], "
                        + "Student [studentNo=null, name=name5, gender=true, age=2]], true=[]}",
                partitioningByMap.toString());
    }
    
    • Collectors.toMap与Collectors.groupingBy/Collectors.partitioningBy不一样,它只负责把集合中的元素根据某种形式拆解为一个Map,该Map的key可以为Null但不允许重复,同时Map的Value不可以为Null

    4. 参考资料

  • 相关阅读:
    走进JavaWeb技术世界8:浅析Tomcat9请求处理流程与启动部署过程
    走进JavaWeb技术世界7:Tomcat和其他WEB容器的区别
    走进JavaWeb技术世界6:Tomcat5总体架构剖析
    走进JavaWeb技术世界5:初探Tomcat的HTTP请求过程
    走进JavaWeb技术世界4:Servlet 工作原理详解
    走进JavaWeb技术世界3:JDBC的进化与连接池技术
    [转]115个Java面试题和答案——终极列表(下)
    [转]115个Java面试题和答案——终极列表(上)
    [转]Spring MVC 4常用的那些注解
    [转]spring4.x注解概述
  • 原文地址:https://www.cnblogs.com/hiver/p/9156147.html
Copyright © 2011-2022 走看看