Collectors.toMap()是 Java 8 Stream API 中用于将流元素收集到 Map 中的收集器。它有多种重载形式。这里主要以3个参数版做介绍,并给出其他参数版本的用法。
1. 三参数版本Collectors.toMap()
1.1 签名(处理重复键)
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, BinaryOperator<U> mergeFunction )泛型参数<T, K, U>的含义:
T: 流中元素的类型(Type of stream elements)K: 生成 Map 的键的类型(Key type)U: 生成 Map 的值的类型(Ualue type)
返回值类型:Collector<T, ?, Map<K,U>>:
- 第一个
T: 输入元素的类型(与流元素类型一致) - 第二个
?: 累加器(accumulator)的类型,这里是通配符,表示我们不关心具体的累加器实现 - 第三个
Map<K,U>: 最终收集结果的类型 - 简单理解:这是一个能将
T类型元素收集到Map<K,U>中的收集器。
第1个入参 Function<? super T, ? extends K> keyMapper:
Function: 函数式接口,接受一个参数,返回一个结果? super T: 表示参数类型可以是T或T的父类(下界通配符)? extends K: 表示返回类型可以是K或K的子类(上界通配符)
- 这个函数的作用:从流中的每个元素
T提取或转换出 Map 的键K
第2个入参 Function<? super T, ? extends U> valueMapper:
- 同理,这个函数的作用:从流中的每个元素
T提取或转换出 Map 的值U
第3个入参:BinaryOperator<U> mergeFunction:
BinaryOperator<U>: 接受两个相同类型U的参数,返回相同类型U结果的函数式接口- 作用:当出现重复键时,如何合并两个值
1.2 使用举例
以学生列表转为 Map举例。
// 学生类 @Data class Student { private String id; private String name; private int age; }使用3个参数版的Collectors.toMap()方法举例:
// 无重复key 使用示例 List<Student> students = Arrays.asList( new Student("001", "Alice", 20), new Student("002", "Bob", 22), new Student("003", "Charlie", 21) ); // 以 ID 为键,Student 对象为值 Map<String, Student> studentMap = students.stream() .collect(Collectors.toMap( Student::getId, // keyMapper: T=Student -> K=String Function.identity(), // valueMapper: T=Student -> U=Student (existing, replacement) -> existing // mergeFunction: 保留已存在的 ));//---------补充说明--------------------------- // Function.identity() 是 Java 8 函数式编程中的一个重要静态方法,可以理解为 "恒等函数" 或 "原样返回函数"。这个函数接收什么就返回什么,不做任何转换。 // 这三种写法是等价的: Function<String, String> func1 = Function.identity(); Function<String, String> func2 = s -> s; Function<String, String> func3 = (String s) -> s;当有重复key时,第3个入参“合并策略”就发挥作用了:
// 有重复key 使用示例: 第3个入参“合并策略”就发挥作用了 List<Student> studentsWithDuplicate = Arrays.asList( new Student("001", "Alice", 20), new Student("002", "Bob", 22), new Student("001", "Alice_new", 25) // 重复的 ID ); // 保留新值(后出现的) Map<String, Student> mapKeepNew = studentsWithDuplicate.stream() .collect(Collectors.toMap( Student::getId, Function.identity(), (oldValue, newValue) -> newValue // 保留新值 )); // 保留旧值(先出现的) Map<String, Student> mapKeepOld = studentsWithDuplicate.stream() .collect(Collectors.toMap( Student::getId, s -> s, (oldValue, newValue) -> oldValue // 保留旧值 ));// 自定义合并逻辑1(比如合并年龄信息) Map<String, Integer> ageSumMap = studentsWithDuplicate.stream() .collect(Collectors.toMap( Student::getId, Student::getAge, // value 是 Integer 类型 Integer::sum // 合并函数:求和 <=> (a1,a2)-> a1+a2 )); // 结果: {"001": 45, "002": 22} // 自定义合并逻辑2 (合并name信息) Map<String, Student> mergeUsers = studentsWithDuplicate.stream() .collect(Collectors.toMap( Student::getId, Function.identity(), (student1, student2) -> { // 比如合并用户信息 return new Student(student1.getId(), student1.getName() + "_" + student1.getName()); } ));2. 其他版本的Collectors.toMap()
2.1 两参数版本
2.1.1 签名(最基础)
public static <T, K, U> Collector<T, ?, Map<K, U>> toMap( Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper )说明
keyMapper: 将流中的每个元素 T 转换为 Map 的键 KvalueMapper: 将流中的每个元素 T 转换为 Map 的值 U- 限制:不允许重复的键,否则抛出
IllegalStateException
2.1.2 使用举例
无重复key 使用示例:
// 基础对象转Map List<Student> students = Arrays.asList( new Student("001", "Alice", 20), new Student("002", "Bob", 22), new Student("003", "Charlie", 21) ); // 姓名作为key,年龄作为value Map<String, Integer> nameToAge = students.stream() .collect(Collectors.toMap(Student::getName, Person::getAge)); // 结果: {Alice=20, Bob=22, Charlie=21} // 对象本身作为value Map<String, Student> nameToPerson = students.stream() .collect(Collectors.toMap(Student::getName, Function.identity())); // 结果: {Alice=Student{id='001', name='Alice', age=20}, ...}有重复key 时会抛异常:
List<Student> studentsWithDuplicate = Arrays.asList( new Student("001", "Alice", 20), new Student("002", "Bob", 22), new Student("001", "Alice_new", 25) // 重复的 ID ); // 这行代码会抛出异常! Map<Long, Student> userMap2 = studentsWithDuplicate.stream() .collect(Collectors.toMap(Student::getId, Function.identity())); // 抛出: IllegalStateException: Duplicate key Student{id=001, name='Alice', age=20}2.2 四参数版本
2.2.1 签名(指定Map实现)
public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap( Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier )说明
- 前三个参数同上
mapSupplier: 提供具体的 Map 实现类的构造函数引用
2.2.2 使用举例
List<Person> people = Arrays.asList( new Person("Charlie", 35), new Person("Alice", 25), new Person("Bob", 30) ); // LinkedHashMap - 保持插入顺序 Map<String, Integer> linkedMap = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (v1, v2) -> v1, LinkedHashMap::new )); // 结果: {Charlie=35, Alice=25, Bob=30} (保持原始顺序)// TreeMap - 按键自然排序 Map<String, Integer> treeMap = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (v1, v2) -> v1, TreeMap::new )); // 结果: {Alice=25, Bob=30, Charlie=35} (按键字母排序)// ConcurrentHashMap - 线程安全 Map<String, Integer> concurrentMap = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (v1, v2) -> v1, ConcurrentHashMap::new ));