Java中的Collectors.toMap()方法用于将流中的元素收集到一个Map中。它需要提供两个函数:一个用于生成键(key),一个用于生成值(value)。此外,还可以提供合并函数(用于处理键冲突)和映射工厂(用于指定具体的Map实现)。

方法签名

  1. 最简单的形式(当键不冲突时):
    Collectors.toMap(Function<? super T, ? extends K> keyMapper,                 Function<? super T, ? extends U> valueMapper)
    
  2. 带合并函数的(用于解决键冲突):
    Collectors.toMap(Function<? super T, ? extends K> keyMapper,                 Function<? super T, ? extends U> valueMapper,                 BinaryOperator<U> mergeFunction)
    
  3. 带合并函数和Map工厂(指定Map的具体实现):
    Collectors.toMap(Function<? super T, ? extends K> keyMapper,                 Function<? super T, ? extends U> valueMapper,                 BinaryOperator<U> mergeFunction,                 Supplier<M> mapSupplier)
    

实例

示例1:基本用法(无冲突)

假设有一个Person类:

class Person {
    private String id;
    private String name;
    // 构造方法、getter和setter省略
}

List<Person>转换为Map<String, String>,其中键是id,值是name:

List<Person> people = Arrays.asList(
    new Person("1", "Alice"),
    new Person("2", "Bob")
);
Map<String, String> idToName = people.stream()
    .collect(Collectors.toMap(Person::getId, Person::getName));
// 输出:{1=Alice, 2=Bob}
示例2:键冲突时使用合并函数

如果流中有两个元素的键相同,则会抛出IllegalStateException。为了避免这种情况,我们可以提供一个合并函数。 例如,有两个人拥有相同的id,我们选择保留后出现的那个人的名字:

List<Person> peopleWithDuplicate = Arrays.asList(
    new Person("1", "Alice"),
    new Person("1", "Bob")
);
Map<String, String> idToName = peopleWithDuplicate.stream()
    .collect(Collectors.toMap(
        Person::getId, 
        Person::getName, 
        (oldValue, newValue) -> newValue)); // 当键冲突时,使用新值覆盖旧值
// 输出:{1=Bob}
示例3:指定Map的具体实现

默认情况下,toMap会生成一个HashMap。但我们可以指定其他Map实现,例如TreeMap

List<Person> people = Arrays.asList(
    new Person("1", "Alice"),
    new Person("2", "Bob")
);
TreeMap<String, String> idToNameTree = people.stream()
    .collect(Collectors.toMap(
        Person::getId, 
        Person::getName, 
        (oldValue, newValue) -> newValue, // 合并函数,这里可以选择覆盖
        TreeMap::new)); // 指定Map实现为TreeMap
// 输出:{1=Alice, 2=Bob},但类型是TreeMap
示例4:更复杂的场景

假设我们有一个订单列表,每个订单有一个订单号和金额。我们想按订单号分组并计算每个订单的总金额。

class Order {
    private String orderId;
    private double amount;
    // 构造方法、getter和setter省略
}
List<Order> orders = Arrays.asList(
    new Order("A", 100.0),
    new Order("A", 50.0),
    new Order("B", 200.0)
);
Map<String, Double> orderTotal = orders.stream()
    .collect(Collectors.toMap(
        Order::getOrderId, // 以订单号作为键
        Order::getAmount,  // 以单个订单金额作为值
        Double::sum        // 当订单号相同时,金额相加
    ));
// 输出:{A=150.0, B=200.0}
示例5:值为对象本身

有时我们希望将对象本身作为值放入Map中。

Map<String, Person> idToPerson = people.stream()
    .collect(Collectors.toMap(Person::getId, person -> person));
// 或者使用方法引用
Map<String, Person> idToPerson2 = people.stream()
    .collect(Collectors.toMap(Person::getId, Function.identity()));

注意事项

  1. 键冲突:如果没有提供合并函数,且出现重复键,会抛出IllegalStateException
  2. 空值:如果键映射函数返回null,会抛出NullPointerException。值映射函数返回null在Java8中允许,但在Java9及以后,如果值映射函数返回null且没有使用合并函数,也会抛出NullPointerException。因此,处理可能为null的情况时,应使用合并函数或提前过滤。
  3. 并行流:合并函数在并行流中用于合并多个部分的结果。

总结

Collectors.toMap()是一个强大的工具,可以灵活地将流转换为Map。使用时注意处理键冲突和空值问题。 Java中Collectors.toMap()的详细用法与实例 Collectors.toMap()是Java流API中非常强大的收集器,用于将流元素转换为Map集合。下面通过多种场景详细说明其用法:

  1. 基础用法(键值映射) java 复制 下载 import java.util.; import java.util.stream.;

class Student { private int id; private String name; private double score;

public Student(int id, String name, double score) {
    this.id = id;
    this.name = name;
    this.score = score;
}

public int getId() { return id; }
public String getName() { return name; }
public double getScore() { return score; }

}

public class ToMapExample { public static void main(String[] args) { List students = Arrays.asList( new Student(1, "Alice", 92.5), new Student(2, "Bob", 85.0), new Student(3, "Charlie", 78.5) );

    // 创建ID->Name的映射
    Map<Integer, String> idToNameMap = students.stream()
        .collect(Collectors.toMap(
            Student::getId,      // 键提取器
            Student::getName     // 值提取器
        ));
    
    System.out.println("ID to Name: " + idToNameMap);
    // 输出: {1=Alice, 2=Bob, 3=Charlie}
    
    // 创建Name->Score的映射
    Map<String, Double> nameToScoreMap = students.stream()
        .collect(Collectors.toMap(
            Student::getName, 
            Student::getScore
        ));
    
    System.out.println("Name to Score: " + nameToScoreMap);
    // 输出: {Alice=92.5, Bob=85.0, Charlie=78.5}
}

} 2. 处理键冲突(合并函数) 当遇到重复键时,需要提供合并函数解决冲突:

java 复制 下载 public class ToMapConflictExample { public static void main(String[] args) { List students = Arrays.asList( new Student(1, "Alice", 92.5), new Student(2, "Bob", 85.0), new Student(3, "Charlie", 78.5), new Student(1, "Alex", 88.0) // 重复ID );

    // 冲突解决方案1:保留第一个值
    Map<Integer, String> keepFirst = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (first, second) -> first  // 保留第一个值
        ));
    System.out.println("Keep first: " + keepFirst);
    // 输出: {1=Alice, 2=Bob, 3=Charlie}
    
    // 冲突解决方案2:保留最后一个值
    Map<Integer, String> keepLast = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (first, second) -> second  // 保留最后一个值
        ));
    System.out.println("Keep last: " + keepLast);
    // 输出: {1=Alex, 2=Bob, 3=Charlie}
    
    // 冲突解决方案3:合并值
    Map<Integer, String> mergeValues = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (first, second) -> first + " & " + second  // 合并值
        ));
    System.out.println("Merge values: " + mergeValues);
    // 输出: {1=Alice & Alex, 2=Bob, 3=Charlie}
    
    // 复杂合并:计算相同ID学生的平均分
    Map<Integer, Double> averageScore = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getScore,
            (oldScore, newScore) -> (oldScore + newScore) / 2
        ));
    System.out.println("Average score: " + averageScore);
    // 输出: {1=90.25, 2=85.0, 3=78.5}
}

} 3. 指定Map实现类型 java 复制 下载 public class ToMapTypeExample { public static void main(String[] args) { List students = Arrays.asList( new Student(1, "Alice", 92.5), new Student(2, "Bob", 85.0), new Student(3, "Charlie", 78.5) );

    // 使用TreeMap按ID排序
    Map<Integer, String> treeMap = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (first, second) -> first,
            TreeMap::new  // 指定Map实现
        ));
    System.out.println("TreeMap: " + treeMap);
    
    // 使用LinkedHashMap保持插入顺序
    Map<Integer, String> linkedHashMap = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (first, second) -> first,
            LinkedHashMap::new
        ));
    System.out.println("LinkedHashMap: " + linkedHashMap);
    
    // 使用ConcurrentHashMap
    Map<Integer, String> concurrentMap = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (first, second) -> first,
            ConcurrentHashMap::new
        ));
    System.out.println("ConcurrentHashMap: " + concurrentMap);
}

} 4. 复杂对象作为值 java 复制 下载 public class ToMapObjectExample { public static void main(String[] args) { List students = Arrays.asList( new Student(1, "Alice", 92.5), new Student(2, "Bob", 85.0), new Student(3, "Charlie", 78.5) );

    // 值映射到整个对象
    Map<Integer, Student> idToStudent = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            student -> student  // 或使用 Function.identity()
        ));
    
    System.out.println("ID to Student:");
    idToStudent.forEach((id, student) -> 
        System.out.println(id + ": " + student.getName()));
    
    // 创建复杂值对象
    Map<String, Map<String, Object>> studentDetails = students.stream()
        .collect(Collectors.toMap(
            Student::getName,
            s -> {
                Map<String, Object> details = new HashMap<>();
                details.put("id", s.getId());
                details.put("score", s.getScore());
                details.put("grade", s.getScore() >= 90 ? "A" : "B");
                return details;
            }
        ));
    
    System.out.println("\nStudent details:");
    studentDetails.forEach((name, details) -> 
        System.out.println(name + ": " + details));
}

} 5. 处理null值 java 复制 下载 public class ToMapNullExample { public static void main(String[] args) { List students = Arrays.asList( new Student(1, "Alice", 92.5), new Student(2, null, 85.0), // 名字为null new Student(3, "Charlie", null) // 分数为null );

    // 1. 过滤null键
    Map<Integer, String> filteredKeys = students.stream()
        .filter(s -> s.getName() != null)
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName
        ));
    System.out.println("Filtered keys: " + filteredKeys);
    
    // 2. 处理null值
    Map<Integer, String> handledValues = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            s -> s.getName() == null ? "Unknown" : s.getName()
        ));
    System.out.println("Handled values: " + handledValues);
    
    // 3. 使用Optional处理null
    Map<Integer, Double> optionalScores = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            s -> Optional.ofNullable(s.getScore()).orElse(0.0)
        ));
    System.out.println("Scores with default: " + optionalScores);
}

} 6. 高级用法:分组与分区 java 复制 下载 public class ToMapAdvancedExample { public static void main(String[] args) { List students = Arrays.asList( new Student(1, "Alice", 92.5), new Student(2, "Bob", 85.0), new Student(3, "Charlie", 78.5), new Student(4, "David", 92.5), new Student(5, "Eva", 78.5) );

    // 按分数分组
    Map<Double, List<Student>> groupByScore = students.stream()
        .collect(Collectors.groupingBy(Student::getScore));
    
    System.out.println("Grouped by score:");
    groupByScore.forEach((score, list) -> 
        System.out.println(score + ": " + 
            list.stream().map(Student::getName).collect(Collectors.joining(", "))));
    
    // 分数分区(及格/不及格)
    Map<Boolean, List<Student>> passFail = students.stream()
        .collect(Collectors.partitioningBy(s -> s.getScore() >= 80));
    
    System.out.println("\nPass/Fail:");
    passFail.forEach((passed, list) -> 
        System.out.println((passed ? "Passed" : "Failed") + ": " + 
            list.stream().map(Student::getName).collect(Collectors.joining(", "))));
    
    // 创建分数->名字列表的映射
    Map<Double, String> scoreToNames = students.stream()
        .collect(Collectors.toMap(
            Student::getScore,
            Student::getName,
            (existing, replacement) -> existing + ", " + replacement
        ));
    
    System.out.println("\nScore to names: " + scoreToNames);
}

} 7. 性能优化技巧 java 复制 下载 public class ToMapPerformanceExample { public static void main(String[] args) { // 创建大量数据 List students = new ArrayList<>(); for (int i = 1; i <= 100000; i++) { students.add(new Student(i, "Student" + i, Math.random() * 100)); }

    // 1. 使用并行流
    long start = System.currentTimeMillis();
    Map<Integer, String> parallelMap = students.parallelStream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName
        ));
    System.out.println("Parallel time: " + (System.currentTimeMillis() - start) + "ms");
    
    // 2. 指定初始容量
    start = System.currentTimeMillis();
    Map<Integer, String> sizedMap = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (f, s) -> s,
            () -> new HashMap<>(students.size()) // 指定初始容量
        ));
    System.out.println("Sized map time: " + (System.currentTimeMillis() - start) + "ms");
    
    // 3. 避免不必要的对象创建
    start = System.currentTimeMillis();
    Map<Integer, String> optimizedMap = new HashMap<>(students.size());
    for (Student s : students) {
        optimizedMap.put(s.getId(), s.getName());
    }
    System.out.println("Traditional loop time: " + (System.currentTimeMillis() - start) + "ms");
}

} 常见错误及解决方案 java 复制 下载 public class ToMapCommonErrors { public static void main(String[] args) { List students = Arrays.asList( new Student(1, "Alice", 92.5), new Student(2, "Bob", 85.0), new Student(2, "Bobby", 90.0) // 重复ID );

    // 错误1:未处理重复键
    try {
        Map<Integer, String> errorMap = students.stream()
            .collect(Collectors.toMap(
                Student::getId,
                Student::getName
            ));
    } catch (IllegalStateException e) {
        System.out.println("Error: " + e.getMessage());
    }
    
    // 解决方案:添加合并函数
    Map<Integer, String> solutionMap = students.stream()
        .collect(Collectors.toMap(
            Student::getId,
            Student::getName,
            (first, second) -> first + "|" + second
        ));
    System.out.println("Solution: " + solutionMap);
    
    // 错误2:空键
    students.add(new Student(3, null, 88.0));
    try {
        Map<Integer, String> nullKeyMap = students.stream()
            .collect(Collectors.toMap(
                s -> s.getName().length(), // 可能抛出NPE
                Student::getName
            ));
    } catch (NullPointerException e) {
        System.out.println("Null key error: " + e.getMessage());
    }
    
    // 解决方案:过滤空值
    Map<Integer, String> safeMap = students.stream()
        .filter(s -> s.getName() != null)
        .collect(Collectors.toMap(
            s -> s.getName().length(),
            Student::getName,
            (f, s) -> f
        ));
    System.out.println("Safe map: " + safeMap);
}

} 总结 Collectors.toMap() 的主要用法:

参数 说明 示例 keyMapper 键提取函数 Student::getId valueMapper 值提取函数 Student::getName mergeFunction 键冲突解决函数 (oldVal, newVal) -> oldVal mapSupplier Map实现工厂 TreeMap::new 最佳实践:

始终处理可能的键冲突

考虑空值情况

对于大型数据集,指定初始容量

使用并行流提高性能(当数据量大时)

考虑使用groupingBy()进行更复杂的分组操作

通过合理使用Collectors.toMap(),可以高效地将流数据转换为Map结构,满足各种业务场景的需求。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐