Skip to content

Java Stream 流收集数据与 Lambda 详解

一、Stream 收集数据概述

Stream 的 .collect() 方法是终端操作,用于将流中的元素汇总为集合、Map、统计值等。

java
List<String> list = people.stream()
    .map(Person::getName)
    .collect(Collectors.toList());  // 收集为List

二、Collectors.toMap() 详解

基本语法

java
Collectors.toMap(
    Function<T, K> keyMapper,      // 如何从T提取key
    Function<T, U> valueMapper,    // 如何从T提取value
    [BinaryOperator<U> mergeFunction],  // 可选:key冲突时如何合并
    [Supplier<M> mapSupplier]      // 可选:指定Map类型
)

1️⃣ 最简形式(一对一映射)

java
// 例:List<Person> -> Map<id, person>
Map<Long, Person> map = people.stream()
    .collect(Collectors.toMap(
        Person::getId,      // key: id
        p -> p              // value: 整个对象
    ));

2️⃣ 提取单个字段作为 value

java
// Map<id, name>
Map<Long, String> map = people.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Person::getName
    ));

3️⃣ Key 冲突处理(必须掌握)

场景:两个元素有相同的 key,会抛 IllegalStateException

java
List<Student> students = List.of(
    new Student("Alice", "A"),
    new Student("Bob", "A"),    // 同grade冲突!
    new Student("Charlie", "B")
);

// ❌ 错误:会抛异常
// students.stream().collect(Collectors.toMap(Student::getGrade, Student::getName));

// ✅ 正确:mergeFunction 解决冲突
Map<String, String> map = students.stream()
    .collect(Collectors.toMap(
        Student::getGrade,    // key
        Student::getName,     // value
        (existing, newVal) -> existing + "," + newVal  // 合并策略
    ));
// 结果: {"A": "Alice,Bob", "B": "Charlie"}

4️⃣ 指定 Map 类型

java
// 返回 TreeMap(按键排序)
Map<Integer, String> sortedMap = people.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Person::getName,
        (a, b) -> a,              // 冲突保留谁
        TreeMap::new             // 指定Map实现
    ));

// 返回 LinkedHashMap(保持插入顺序)
Map<Integer, Person> orderedMap = people.stream()
    .collect(Collectors.toMap(
        Person::getId,
        p -> p,
        (a, b) -> a,
        LinkedHashMap::new
    ));

// 返回 ConcurrentHashMap
Map<Long, String> concurrentMap = people.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Person::getName,
        (a, b) -> a,
        ConcurrentHashMap::new
    ));

三、常见收集场景汇总

需求代码
转 List.collect(Collectors.toList())
转 Set.collect(Collectors.toSet())
转 Collection.collect(Collectors.toCollection(LinkedList::new))
分组.collect(Collectors.groupingBy(Person::getDept))
分区(boolean).collect(Collectors.partitioningBy(Person::isActive))
计数.collect(Collectors.counting())
求和.collect(Collectors.summingInt(Person::getAge))
拼接字符串.collect(Collectors.joining(", "))

四、常见错误与解决

错误原因解决
NullPointerExceptionkey 或 value 为 nulltoMap(...).filterKeys(Objects::nonNull)
IllegalStateExceptionkey 重复且无 merge 函数添加 (k1, k2) -> k1 等冲突处理
ClassCastExceptionkey 类型不匹配检查 keyMapper 返回类型

五、从匿名内部类到 Lambda 的演变

演变四阶段

Stage 1:匿名内部类(最原始)

java
// 接口定义
interface Converter<T, U> {
    U convert(T input);
}

// 使用匿名内部类
Converter<String, Integer> converter = new Converter<String, Integer>() {
    @Override
    public Integer convert(String input) {
        return Integer.parseInt(input);
    }
};

Integer result = converter.convert("123");

Stage 2:简化匿名内部类

java
// 当接口只有一个抽象方法时,可省略类型声明
Converter<String, Integer> converter = new Converter<>() {
    @Override
    public Integer convert(String input) {
        return Integer.parseInt(input);
    }
};

Stage 3:Lambda 表达式(过渡写法)

java
// 用 Lambda 替代整个匿名内部类
Converter<String, Integer> converter = (String input) -> {
    return Integer.parseInt(input);
};

Stage 4:Lambda 表达式(最终形态)

java
// 类型推断:可省略参数类型
Converter<String, Integer> converter = (input) -> Integer.parseInt(input);

// 单参数:可省略括号
Converter<String, Integer> converter = input -> Integer.parseInt(input);

Collector 中的转变

java
// ❌ 匿名内部类(啰嗦)
Map<Long, String> map1 = people.stream()
    .collect(Collectors.toMap(
        new Function<Person, Long>() {
            @Override
            public Long apply(Person p) {
                return p.getId();
            }
        },
        new Function<Person, String>() {
            @Override
            public String apply(Person p) {
                return p.getName();
            }
        },
        new BinaryOperator<String>() {
            @Override
            public String apply(String s1, String s2) {
                return s1;
            }
        }
    ));

// ✅ Lambda 表达式(简洁)
Map<Long, String> map2 = people.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Person::getName,
        (s1, s2) -> s1
    ));

演变规律总结

匿名内部类Lambda
new Interface() { ... }(params) -> { ... }
obj.method(new Interface(){ ... })obj.method((params) -> ...)
显式声明类型自动类型推断
return 语句直接表达式(可省略 return)
p -> p.getName()Class::method

方法引用(最简形式)

场景Lambda方法引用
对象方法p -> p.getName()Person::getName
静态方法s -> Integer.parseInt(s)Integer::parseInt
构造方法s -> new Person(s)Person::new
无参数方法() -> p.getList()p::getList
java
// 最终最简写法
Map<Long, String> map = people.stream()
    .collect(Collectors.toMap(
        Person::getId,    // Person::getId 等价于 p -> p.getId()
        Person::getName   // Person::getName 等价于 p -> p.getName()
    ));

六、一句话总结

匿名内部类 → Lambda:只有单抽象方法接口(SAM) 才能用,编译器自动推断类型,语法从 new Interface(){ @Override ... } 简化为 (params) -> expressionobj::method


相关标签

#Java #Stream #Lambda #Collectors #函数式编程

Knowledge4J — Java 知识库