Java Stream 初解
目录
一、什么是 Stream?
二、Stream 的核心特性
三、Stream 流水线
1. 源 (Source)
2. 中间操作 (Intermediate Operations)
3. 终端操作 (Terminal Operation)
四、常用 Stream 操作
1. 过滤与映射
2.归约操作
3.并行流
一、什么是 Stream?
Stream(流)是 Java 8 中引入的一个全新概念,它代表支持顺序和并行聚合操作的元素序列。与集合 (Collection) 不同:
- 集合关注元素的存储和访问
- Stream 关注元素的计算和处理
Stream 本身并不存储元素,它更像一个高级迭代器,能够以声明式方式描述对数据源的一系列操作。
// 一个简单的Stream示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
long count = names.stream().filter(name -> name.length() > 4) // 过滤出长度大于4的名字.count(); // 计数
System.out.println(count); // 输出:2("Alice"和"Charlie")
二、Stream 的核心特性
惰性求值:中间操作不会立即执行,只有当终端操作被调用时,所有中间操作才会一次性执行("延迟执行")。
流水线操作:Stream 操作可以串联成一个流水线(pipeline),形成清晰的操作链。
一次性使用:一个 Stream 只能被操作一次,再次使用会抛出
IllegalStateException
。
支持并行:无需额外代码,即可轻松实现并行处理(利用多线程)。
无存储:Stream 不存储元素,元素只在需要时才被计算。
三、Stream 流水线
一个完整的 Stream 操作由三部分组成:
源 (Source)→中间操作 (Intermediate Operations)→终端操作 (Terminal Operation)
1. 源 (Source)
Stream 的数据源可以是:
- 集合(
collection.stream()
或collection.parallelStream()
)- 数组(
Arrays.stream(array)
)- 生成器函数(
Stream.generate()
)- I/O 通道(如
Files.lines(Path)
)
// 从集合创建
List<Integer> list = Arrays.asList(1, 2, 3);
Stream<Integer> streamFromList = list.stream();// 从数组创建
int[] array = {1, 2, 3};
IntStream streamFromArray = Arrays.stream(array);// 从生成器创建(无限流)
Stream<Double> randomStream = Stream.generate(Math::random);
2. 中间操作 (Intermediate Operations)
中间操作用于转换 Stream,返回一个新的 Stream。常见的中间操作包括
操作 描述 filter(Predicate)
过滤出满足条件的元素 map(Function)
对元素执行映射转换 flatMap(Function)
将元素转换为 Stream 并扁平化 distinct()
去重(基于 equals()
)sorted()
排序(自然顺序或指定比较器) limit(long)
截断流,保留前 n 个元素 skip(long)
跳过前 n 个元素
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");Stream<String> processedStream = words.stream().filter(word -> word.length() > 5) // 保留长度>5的单词.map(String::toUpperCase) // 转换为大写.distinct() // 去重(这里示例中无重复,仅作演示).sorted(); // 排序
3. 终端操作 (Terminal Operation)
终端操作会触发整个流水线的执行,并产生一个结果或副作用。执行后,Stream 将不可再用。
常见的终端操作包括:
操作 描述 forEach(Consumer)
遍历元素执行操作 count()
返回元素数量 collect(Collector)
收集元素到集合或其他结构 reduce(BinaryOperator)
归约操作,产生单个结果 min(Comparator)
/max(Comparator)
查找最小 / 最大值 anyMatch(Predicate)
/allMatch(Predicate)
/noneMatch(Predicate)
匹配检查 findFirst()
/findAny()
查找元素
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");// 1. 收集到List List<String> longFruits = fruits.stream().filter(f -> f.length() > 5).collect(Collectors.toList());// 2. 归约求和(字符串长度总和) int totalLength = fruits.stream().mapToInt(String::length).sum();// 3. 匹配检查 boolean hasShortFruit = fruits.stream().anyMatch(f -> f.length() <= 4); // "date"长度为4,返回true
四、常用 Stream 操作
1. 过滤与映射
这是最常用的组合操作,先过滤出需要的元素,再进行转换处理。
// 示例:找出所有偶数并计算它们的平方和
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sumOfEvenSquares = numbers.stream().filter(n -> n % 2 == 0) // 过滤偶数.mapToInt(n -> n * n) // 计算平方.sum(); // 求和System.out.println(sumOfEvenSquares); // 输出:220(2²+4²+6²+8²+10²)
2.归约操作
reduce()
用于将 Stream 中的元素通过累加器组合成一个结果。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);// 1. 计算总和(有初始值)
int sumWithIdentity = numbers.stream().reduce(0, Integer::sum); // 0为初始值,累加所有元素// 2. 计算最大值(无初始值,返回Optional)
Optional<Integer> max = numbers.stream().reduce(Integer::max);// 3. 拼接字符串
List<String> words = Arrays.asList("Hello", " ", "World", "!");
String sentence = words.stream().reduce("", String::concat); // 结果:"Hello World!"
3.并行流
Stream API 最强大的特性之一是对并行处理的支持。只需将stream()
改为parallelStream()
,即可实现并行操作。
// 并行计算1到1000000的总和
long sum = IntStream.rangeClosed(1, 1000000).parallel() // 转为并行流.sum();System.out.println(sum); // 输出:500000500000
注意:并行流使用公共的 ForkJoinPool,适用于 CPU 密集型任务。对于 IO 密集型任务,效果可能不佳甚至更差。
并行流就像一个公共的 "多人工厂",适合干 "费脑子但不费时间等" 的活,不适合干 "大部分时间在等" 的活。
具体来说:
1. CPU 密集型任务:比如复杂计算、数据处理等,这些任务主要靠 CPU"使劲算",几乎不需要等待。
- 这时候用并行流(多线程)能明显提高效率,就像多个人同时埋头算账,肯定比一个人算得快。
2. IO 密集型任务:比如网络请求、文件读写等,这些任务大部分时间都在 "等待"(等数据传输、等磁盘响应)。
- 这时候用并行流效果不好,因为就算开多个线程,大部分时间还是在等,多线程的优势发挥不出来,反而会因为线程切换增加额外开销,就像多个人一起等快递,并不会比一个人等来得更快。
举个生活例子:
- 算 100 道数学题(CPU 密集):5 个人同时算比 1 个人算快很多
- 打 100 个电话(IO 密集,大部分时间在等对方接):5 个人打并不比 1 个人打快多少,反而可能因为协调问题更麻烦