"Stream API 是Java 8最革命性的改变,它让Java拥有了现代化的数据处理能力" ------ Martin Fowler
一、Stream API 是什么?
Stream API 是Java 8引入的一套函数式数据处理工具,它允许你以声明式方式处理集合数据。与传统循环相比,Stream API 更简洁、更易读,并且能轻松利用多核架构。
核心特点:
-
非数据结构:不存储数据,只定义操作
-
不修改源数据:所有操作返回新Stream
-
惰性执行:终端操作触发实际计算
-
可并行化:parallel()开启并行处理
二、Stream 操作分类
操作类型 | 特点 | 常见方法 |
---|---|---|
创建操作 | 创建Stream | stream() , of() , generate() , iterate() |
中间操作 | 定义处理链 | filter() , map() , flatMap() , distinct() , sorted() , limit() |
终端操作 | 触发计算 | forEach() , collect() , reduce() , count() , anyMatch() |
三、创建 Stream 的 7 种方式
java
// 1. 从集合创建
List<String> list = Arrays.asList("Java", "Python", "C++");
Stream<String> stream1 = list.stream();
// 2. 从数组创建
String[] array = {"Apple", "Banana", "Orange"};
Stream<String> stream2 = Arrays.stream(array);
// 3. 使用Stream.of()
Stream<String> stream3 = Stream.of("A", "B", "C");
// 4. 生成常量流
Stream<String> constStream = Stream.generate(() -> "Hello").limit(5);
// 5. 生成数列
Stream<Integer> numStream = Stream.iterate(0, n -> n + 2).limit(10);
// 6. 从文件创建
Path path = Paths.get("data.txt");
Stream<String> fileStream = Files.lines(path);
// 7. 基本类型流(避免装箱开销)
IntStream intStream = IntStream.range(1, 100);
四、核心中间操作详解
1. 过滤 - filter()
java
// 保留长度大于4的字符串
List<String> longNames = list.stream()
.filter(s -> s.length() > 4)
.collect(Collectors.toList());
2. 映射 - map()
java
// 将字符串转为大写
List<String> upperCase = list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
3. 扁平化 - flatMap()
java
// 合并多个集合
List<List<String>> nestedList = ...;
List<String> flatList = nestedList.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
4. 去重 - distinct()
java
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> unique = numbers.stream()
.distinct()
.collect(Collectors.toList()); // [1, 2, 3]
5. 排序 - sorted()
java
// 按长度排序
List<String> sorted = list.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
五、终端操作实战
1. 收集结果 - collect()
java
// 转为List
List<String> resultList = stream.collect(Collectors.toList());
// 转为Set
Set<String> resultSet = stream.collect(Collectors.toSet());
// 转为Map
Map<String, Integer> nameMap = list.stream()
.collect(Collectors.toMap(
Function.identity(), // 键
String::length // 值
));
// 分组
Map<Integer, List<String>> groupByLength = list.stream()
.collect(Collectors.groupingBy(String::length));
// 分区(按条件分组)
Map<Boolean, List<String>> partition = list.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 4));
2. 聚合计算 - reduce()
java
// 求和
int sum = IntStream.range(1, 10).reduce(0, (a, b) -> a + b);
// 连接字符串
String concat = list.stream()
.reduce("", (a, b) -> a + "," + b);
3. 匹配检查
java
boolean allLong = list.stream().allMatch(s -> s.length() > 3); // 所有元素满足条件
boolean anyLong = list.stream().anyMatch(s -> s.length() > 5); // 任一元素满足条件
boolean noneEmpty = list.stream().noneMatch(String::isEmpty); // 没有空字符串
4. 查找元素
java
Optional<String> firstLong = list.stream()
.filter(s -> s.length() > 5)
.findFirst(); // 获取第一个
Optional<String> anyLong = list.stream()
.filter(s -> s.length() > 5)
.findAny(); // 获取任意一个(并行流更高效)
六、并行流:释放多核威力
java
// 顺序流
long start = System.currentTimeMillis();
list.stream().forEach(...);
long seqTime = System.currentTimeMillis() - start;
// 并行流
start = System.currentTimeMillis();
list.parallelStream().forEach(...);
long parTime = System.currentTimeMillis() - start;
System.out.println("并行加速比:" + (double)seqTime/parTime);
使用建议:
-
数据量 > 10000 时效果明显
-
避免共享可变状态
-
操作应独立无状态
-
避免I/O密集型操作
七、实战案例:电商数据分析
java
List<Product> products = Arrays.asList(
new Product("Laptop", 1200, "Electronics"),
new Product("Shirt", 50, "Fashion"),
new Product("Phone", 800, "Electronics"),
new Product("Book", 20, "Education")
);
// 案例1:计算电子类产品平均价格
double avgPrice = products.stream()
.filter(p -> "Electronics".equals(p.getCategory()))
.mapToDouble(Product::getPrice)
.average()
.orElse(0.0);
// 案例2:按类别分组并统计
Map<String, DoubleSummaryStatistics> statsByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.summarizingDouble(Product::getPrice)
));
// 案例3:获取最贵的3个产品
List<Product> top3 = products.stream()
.sorted(Comparator.comparingDouble(Product::getPrice).reversed())
.limit(3)
.collect(Collectors.toList());
// 案例4:并行处理百万数据
List<Product> bigData = // 百万级数据
Map<String, List<Product>> categoryMap = bigData.parallelStream()
.collect(Collectors.groupingByConcurrent(Product::getCategory));
八、性能优化指南
-
避免装箱开销:使用IntStream/LongStream/DoubleStream
java// 低效 list.stream().mapToInt(s -> s.length()).sum(); // 高效 list.stream().mapToInt(String::length).sum();
合并操作 :减少中间操作数量
java
// 优化前:两次遍历
long count = list.stream().filter(s -> s.length() > 3).count();
// 优化后:一次遍历
boolean exists = list.stream().anyMatch(s -> s.length() > 3);
-
短路操作优先:findFirst/anyMatch代替collect
java// 找到第一个即停止 Optional<String> result = list.stream() .filter(s -> s.contains("error")) .findFirst();
-
并行流注意点:
-
小数据量(<1000)不要用
-
避免共享状态
-
确保操作可并行化
-
九、Stream vs 传统循环对比
场景 | 传统循环 | Stream API |
---|---|---|
简单遍历 | ✅ 更直接 | ⚠️ 稍重 |
复杂数据处理 | ❌ 嵌套难读 | ✅ 声明式清晰 |
并行处理 | ❌ 复杂易错 | ✅ 一行代码 |
延迟处理 | ❌ 立即执行 | ✅ 惰性求值 |
代码复用 | ❌ 困难 | ✅ 管道组合 |
十、最佳实践总结
-
保持简洁:单个Stream操作不超过5个步骤
-
避免副作用:不要在lambda中修改外部状态
-
优先使用方法引用 :
String::length
比s -> s.length()
更简洁 -
合理使用Optional:安全处理可能为空的结果
-
适时使用并行:大数据集才值得
黄金法则:Stream API 不是要完全取代循环,而是在处理复杂数据管道时提供更优雅的解决方案。
动手练习:使用Stream API实现以下功能
// 给定数字列表:1, 2, 3, 4, 5, 6, 7, 8, 9
// 1. 找出所有偶数
// 2. 每个数字平方
// 3. 忽略前2个结果
// 4. 计算剩余数字的平均值