Java 8中Collection轉爲Map的方法

Java 8中java.util.stream.Collectors提供了幾個方法可用於把Collection轉爲Map結構,本文記錄了我的對其中三個的理解。java

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

1. 環境

Java: jdk1.8.0_144git

2. 特性說明

Student.javagithub

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()方法app

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的問題,能夠有兩個選擇ide

  • 肯定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. 參考資料

  • 附代碼地址
    https://github.com/hivsuper/study/blob/master/study-java8/src/test/java/org/lxp/java8/map/CollectionToMapTest.java
  • https://stackoverflow.com/questions/27993604/whats-the-purpose-of-partitioningby
相關文章
相關標籤/搜索