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(", ")) |
四、常见错误与解决
| 错误 | 原因 | 解决 |
|---|---|---|
NullPointerException | key 或 value 为 null | 用 toMap(...).filterKeys(Objects::nonNull) |
IllegalStateException | key 重复且无 merge 函数 | 添加 (k1, k2) -> k1 等冲突处理 |
ClassCastException | key 类型不匹配 | 检查 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) -> expression或obj::method。
相关标签
#Java #Stream #Lambda #Collectors #函数式编程