Java 8中Collection转为Map的方法

java

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

MethodReturn Type
groupingByMap<K, List<T>>
partitioningByMap<Boolean, List<T>>
toMapMap<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. 参考资料

  • 附代码地址
    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

以上是 Java 8中Collection转为Map的方法 的全部内容, 来源链接: utcz.com/z/391856.html

回到顶部