流式编程基础示例
本节通过一个完整的整数处理案例,演示Java流式编程的核心操作流程。该示例将读取整数列表,计算其中所有奇数的平方和,涵盖从流创建到终端操作的完整处理链。
流创建与数据源
Collection
接口的stream()
方法可将集合转换为顺序流。以下代码创建包含1到5的整数列表,并通过stream()
方法生成流对象:
// 创建1到5的整数列表
List numbersList = List.of(1, 2, 3, 4, 5);
// 从列表获取流对象
Stream numbersStream = numbersList.stream();
元素过滤操作
Stream
接口的filter()
方法接收Predicate
参数,返回符合断言条件的元素流。Lambda表达式n -> n % 2 == 1
用于筛选奇数:
// 获取奇数流
Stream oddNumbersStream = numbersStream.filter(n -> n % 2 == 1);
元素转换操作
map()
方法通过Function
参数对元素进行转换。下列代码将奇数流中的每个元素映射为其平方值:
// 获取奇数平方流
Stream squaredNumbersStream = oddNumbersStream.map(n -> n * n);
归约求和操作
reduce()
方法通过二元操作符对流进行归约计算,其工作流程包含:
- 接收初始值(identity)和累加器(BinaryOperator)
- 首次计算:初始值与首元素运算
- 后续计算:前次结果与当前元素运算
- 返回最终累加结果
基础实现与方法引用优化版本:
// 基础Lambda实现
int sum = squaredNumbersStream.reduce(0, (n1, n2) -> n1 + n2);
// 使用方法引用优化
int sum = squaredNumbersStream.reduce(0, Integer::sum);
链式调用实践
流操作支持方法链式调用,可消除中间变量。完整处理链可简化为单条语句:
int sum = numbersList.stream()
.filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0, Integer::sum);
完整示例程序
// SquaredIntsSum.java
public class SquaredIntsSum {
public static void main(String[] args) {
List numbers = List.of(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0, Integer::sum);
System.out.println("Sum = " + sum); // 输出:Sum = 35
}
}
该示例展示了流式编程的典型处理模式:数据源→中间操作(过滤/转换)→终端操作(归约)。实际开发中,对于数值型流建议使用IntStream
等专用流类型以获得更好性能。
Person类实战案例
类结构设计
Person类作为流式编程的典型业务对象,采用以下核心设计:
- Gender枚举:明确定义性别常量
public static enum Gender {
MALE, FEMALE
}
- 核心字段:
- id:唯一标识符(long)
- name:姓名(String)
- gender:性别(Gender枚举)
- dob:出生日期(LocalDate)
- income:收入(double)
- 辅助方法:
// 性别判断方法(用于流式过滤)
public boolean isMale() {
return this.gender == Gender.MALE;
}
public boolean isFemale() {
return this.gender == Gender.FEMALE;
}
测试数据生成
静态方法persons()
生成包含6条记录的测试数据集,涵盖不同性别、年龄和收入组合:
public static List persons() {
Person ken = new Person(1, "Ken", Gender.MALE,
LocalDate.of(1970, Month.MAY, 4), 6000.0);
Person donna = new Person(3, "Donna", Gender.FEMALE,
LocalDate.of(1962, Month.JULY, 29), 8700.0);
// 其他人员实例...
return List.of(ken, jeff, donna, chris, laynie, lee);
}
流式处理模式
基础过滤应用
// 获取所有女性
List females = Person.persons().stream()
.filter(Person::isFemale)
.collect(Collectors.toList());
// 获取高收入人群(收入>5000)
List highEarners = Person.persons().stream()
.filter(p -> p.getIncome() > 5000)
.collect(Collectors.toList());
多条件组合查询
// 获取1990年后出生的男性
List youngMales = Person.persons().stream()
.filter(p -> p.isMale() &&
p.getDob().getYear() > 1990)
.collect(Collectors.toList());
输出格式化
重写的toString()
方法统一了对象输出格式:
@Override
public String toString() {
return String.format("(%d, %s, %s, %s, %.2f)",
id, name, gender, dob, income);
}
输出示例:(3, Donna, FEMALE, 1962-07-29, 8700.00)
典型应用场景
数据分组统计
// 按性别统计人数
Map genderCount = Person.persons().stream()
.collect(Collectors.groupingBy(
Person::getGender,
Collectors.counting()));
最高收入查询
// 各性别最高收入者
Map> topEarners =
Person.persons().stream()
.collect(Collectors.groupingBy(
Person::getGender,
Collectors.maxBy(
Comparator.comparingDouble(Person::getIncome))));
设计要点
- 不变性设计:所有字段通过构造器注入,避免状态变更
- 方法引用优化:isMale/isFemale方法专为流式过滤设计
- 测试数据封装:静态工厂方法保证数据一致性
- 格式化输出:统一的对象字符串表示形式
该类的设计充分考虑了流式编程的需求,通过合理的API设计使得在流操作中可以:
- 使用
方法引用
替代Lambda表达式(如Person::isFemale
) - 支持多级条件过滤
- 方便进行分组统计等聚合操作
- 保持输出格式的一致性
数据收集到Map
toMap方法重载形式
Collectors.toMap()
方法提供三种重载实现,支持不同粒度的Map收集操作:
// 基础版本(键值映射器)
toMap(Function keyMapper,
Function valueMapper)
// 带合并函数版本
toMap(Function keyMapper,
Function valueMapper,
BinaryOperator mergeFunction)
// 自定义Map实现版本
toMap(Function keyMapper,
Function valueMapper,
BinaryOperator mergeFunction,
Supplier mapSupplier)
键冲突处理方案
当出现重复键时,基础版本会抛出IllegalStateException
。通过merge函数可解决此问题,该函数接收旧值和新值,返回合并后的最终值:
// 按性别分组并合并姓名
Map genderToNames = persons.stream()
.collect(Collectors.toMap(
Person::getGender,
Person::getName,
(oldVal, newVal) -> String.join(", ", oldVal, newVal)));
// 输出:{FEMALE=Donna, Laynie, MALE=Ken, Jeff, Chris, Li}
统计型收集案例
按性别统计人数
Map genderCount = persons.stream()
.collect(Collectors.toMap(
Person::getGender,
p -> 1L,
Long::sum));
// 输出:{MALE=4, FEMALE=2}
获取最高收入者
Map topEarner = persons.stream()
.collect(Collectors.toMap(
Person::getGender,
Function.identity(),
(p1, p2) -> p1.getIncome() > p2.getIncome() ? p1 : p2));
// 输出:{FEMALE=(3, Donna, FEMALE, 1962-07-29, 8700.00),
// MALE=(2, Jeff, MALE, 1970-07-15, 7100.00)}
并发收集优化
对于并行流处理,建议使用toConcurrentMap()
替代toMap()
以获得更好的性能:
ConcurrentMap concurrentMap = persons
.parallelStream()
.collect(Collectors.toConcurrentMap(
Person::getGender,
Person::getName,
(a,b) -> String.join(",", a,b)));
实现原理剖析
键值映射阶段:
keyMapper
将流元素转换为Map键valueMapper
将元素转换为Map值
冲突处理阶段:
- 当新键已存在时触发merge函数
- 函数接收当前键对应的旧值和新插入值
- 返回结果将作为该键的最终值
结果构建阶段:
- 默认使用HashMap存储结果
- 可通过
mapSupplier
指定TreeMap等实现
典型应用场景
- 对象属性提取:
// ID到姓名的映射
Map idToName = persons.stream()
.collect(toMap(Person::getId, Person::getName));
- 分组聚合计算:
// 各性别总收
Map incomeByGender = persons.stream()
.collect(toMap(
Person::getGender,
Person::getIncome,
Double::sum));
- 索引构建:
// 出生年份索引
Map> yearIndex = persons.stream()
.collect(groupingBy(p -> p.getDob().getYear()));
注意事项
空值处理:
- keyMapper和valueMapper返回null会抛出NPE
- 需提前使用
filter
排除null值
有序性保证:
- 基础版本不保证插入顺序
- 需要有序结果时应使用LinkedHashMap:
.toMap(
keyMapper,
valueMapper,
mergeFunction,
LinkedHashMap::new)
- 性能考量:
- 大数据集建议指定初始容量
- 并行流优先考虑ConcurrentMap实现
通过合理选择toMap的不同重载形式,可以高效实现从简单映射到复杂聚合的各种Map收集需求。
高级收集器技术
collect()方法重载形式
Stream接口提供两种collect()
方法重载实现:
// 基础版本(三要素构造)
R collect(Supplier supplier,
BiConsumer accumulator,
BiConsumer combiner)
// 优化版本(使用Collector)
R collect(Collector collector)
基础版本要求开发者显式提供:
- Supplier:构造结果容器(如
ArrayList::new
) - Accumulator:元素累加逻辑(如
List::add
) - Combiner:并行流合并策略(如
List::addAll
)
典型实现示例:
List names = persons.stream()
.map(Person::getName)
.collect(ArrayList::new,
ArrayList::add,
ArrayList::addAll);
Collectors工具类核心方法
java.util.stream.Collectors
提供常用收集器实现:
基础收集
// 转为List
Collectors.toList()
// 转为Set(自动去重)
Collectors.toSet()
// 自定义集合类型
Collectors.toCollection(TreeSet::new)
聚合统计
// 元素计数
Collectors.counting()
// 求和操作
Collectors.summingInt(Person::getAge)
// 求平均值
Collectors.averagingDouble(Person::getIncome)
字符串连接
// 简单连接
Collectors.joining()
// 带分隔符连接
Collectors.joining(", ")
// 带前后缀的连接
Collectors.joining(", ", "[", "]")
排序收集实现方案
方案一:通过结果容器排序
// 使用TreeSet自动排序
SortedSet sortedNames = persons.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));
方案二:通过流操作排序
// 先排序再收集
List sortedNames = persons.stream()
.map(Person::getName)
.sorted() // 自然排序
.collect(Collectors.toList());
// 自定义比较器排序
List sortedByIncome = persons.stream()
.sorted(Comparator.comparingDouble(Person::getIncome))
.collect(Collectors.toList());
嵌套收集器设计模式
Collectors支持嵌套组合实现复杂聚合:
分组统计
// 按性别分组求平均收入
Map avgIncomeByGender = persons.stream()
.collect(Collectors.groupingBy(
Person::getGender,
Collectors.averagingDouble(Person::getIncome)));
多级分组
// 先按性别再按年龄分段分组
Map>> multiLevel = persons.stream()
.collect(Collectors.groupingBy(
Person::getGender,
Collectors.groupingBy(p ->
p.getAge() > 30 ? "Senior" : "Junior")));
分区统计
// 按收入是否高于5000分区计数
Map partitionCount = persons.stream()
.collect(Collectors.partitioningBy(
p -> p.getIncome() > 5000,
Collectors.counting()));
性能优化建议
- 并行流处理:
// 使用并发安全收集器
ConcurrentMap> parallelResult =
persons.parallelStream()
.collect(Collectors.toConcurrentMap(
Person::getGender,
Collections::singletonList,
(a,b) -> { List merged = new ArrayList<>(a); merged.addAll(b); return merged; }));
- 预分配容量:
// 优化ArrayList收集性能
List optimizedList = persons.stream()
.collect(Collectors.toCollection(() -> new ArrayList<>(persons.size())));
- 短路操作组合:
// 查找收入最高的3人
List top3 = persons.stream()
.sorted(Comparator.comparingDouble(Person::getIncome).reversed())
.limit(3)
.collect(Collectors.toList());
流式编程核心要点总结
三阶段处理模型
流式编程遵循严格的"创建-处理-收集"三阶段模型:
- 流创建阶段:通过集合、数组或生成器创建数据源
- 中间操作阶段:包含过滤(filter)、映射(map)、排序(sorted)等惰性操作
- 终止操作阶段:触发实际计算,如归约(reduce)、收集(collect)等
// 典型处理链示例
List result = dataSource.stream() // 创建
.filter(x -> x > threshold) // 中间
.map(Object::toString) // 中间
.collect(Collectors.toList()); // 终止
收集器框架价值
Collectors工具类提供强大的数据聚合能力:
- 多级收集:支持嵌套收集器实现复杂聚合
- 并行优化:提供并发安全版本(toConcurrentMap等)
- 类型转换:通过finisher函数转换结果类型
- 统计功能:内置求和、平均、极值等统计操作
// 多维度统计示例
Map stats = persons.stream()
.collect(Collectors.groupingBy(
Person::getGender,
Collectors.summarizingDouble(Person::getIncome)));
方法引用优化
方法引用显著提升代码可读性:
- 静态方法引用:
Integer::parseInt
- 实例方法引用:
String::length
- 构造函数引用:
ArrayList::new
- 特定对象方法引用:
person::getName
与Lambda表达式对比:
// Lambda表达式
.filter(p -> p.isFemale())
// 方法引用优化
.filter(Person::isFemale)
并行处理注意事项
- 线程安全:
- 避免有状态Lambda
- 使用并发收集器(toConcurrentMap)
- 性能考量:
- 小数据集可能产生负优化
- 注意任务分解开销
- 顺序保证:
- forEachOrdered保持顺序
- 无序流可提升性能(distinct等操作)
// 并行流正确用法
ConcurrentMap> parallelResult = persons
.parallelStream()
.collect(Collectors.groupingByConcurrent(Person::getGender));
工程实践建议
- 防御性编程:
- 处理Optional返回值
- 预防NPE(使用Objects.requireNonNull)
- 性能监控:
- 记录流操作耗时
- 避免复杂中间操作链
- 代码可维护性:
- 限制操作链长度(建议不超过5个)
- 为复杂收集器添加注释
- 异常处理:
- 包装检查异常
- 使用异常处理中间操作
// 健壮的流处理示例
try {
List = data.stream()
.map(this::safeTransform) // 封装可能抛异常的方法
.filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (ProcessingException e) {
// 统一异常处理
}
流式编程通过声明式语法和函数式风格,配合Collectors强大的聚合能力,能够显著提升数据处理代码的表达力和可维护性。在实际工程中应特别注意并行流的安全性和性能特性,合理使用方法引用等优化手段,使代码既简洁又高效。