写在前面
欢迎来到JDK8新特性系列教程的第四天!在前面的学习中,我们已经掌握了Lambda表达式、函数式接口和方法引用。今天,我们将学习JDK8中最强大、最实用的特性------Stream API。
Stream API可以说是JDK8给Java程序员带来的最大礼物。它彻底改变了我们处理集合数据的方式,让复杂的数据操作变得简洁、优雅且高效。如果你还在使用传统的for循环处理集合数据,那么今天的内容将为你打开一扇新的大门。
准备好了吗?让我们一起探索Stream API的强大功能!

目录
-
- 写在前面
- [一、为什么需要Stream API](#一、为什么需要Stream API)
-
- [1.1 传统集合操作的痛点](#1.1 传统集合操作的痛点)
- [1.2 Stream API的优势](#1.2 Stream API的优势)
- [1.3 什么是Stream](#1.3 什么是Stream)
- 二、Stream的创建方式
-
- [2.1 从集合创建](#2.1 从集合创建)
- [2.2 从数组创建](#2.2 从数组创建)
- [2.3 使用Stream.of创建](#2.3 使用Stream.of创建)
- [2.4 使用Stream.generate和Stream.iterate创建无限流](#2.4 使用Stream.generate和Stream.iterate创建无限流)
- [2.5 从I/O和其他源创建](#2.5 从I/O和其他源创建)
- [2.6 创建方式对比表](#2.6 创建方式对比表)
- 三、Stream的中间操作
-
- [3.1 filter - 过滤](#3.1 filter - 过滤)
- [3.2 map - 映射](#3.2 map - 映射)
- [3.3 flatMap - 扁平化映射](#3.3 flatMap - 扁平化映射)
- [3.4 distinct - 去重](#3.4 distinct - 去重)
- [3.5 sorted - 排序](#3.5 sorted - 排序)
- [3.6 peek - 查看元素](#3.6 peek - 查看元素)
- [3.7 limit和skip - 截取](#3.7 limit和skip - 截取)
- [3.8 中间操作对比表](#3.8 中间操作对比表)
- 四、Stream的终止操作
-
- [4.1 forEach - 遍历](#4.1 forEach - 遍历)
- [4.2 collect - 收集](#4.2 collect - 收集)
- [4.3 toArray - 转换为数组](#4.3 toArray - 转换为数组)
- [4.4 reduce - 归约](#4.4 reduce - 归约)
- [4.5 min/max/count - 统计](#4.5 min/max/count - 统计)
- [4.6 匹配操作](#4.6 匹配操作)
- [4.7 findFirst/findAny - 查找](#4.7 findFirst/findAny - 查找)
- [4.8 终止操作对比表](#4.8 终止操作对比表)
- 五、经验之谈:Stream操作是惰性求值的
-
- [5.1 什么是惰性求值](#5.1 什么是惰性求值)
- [5.2 短路操作的优势](#5.2 短路操作的优势)
- [5.3 短路操作列表](#5.3 短路操作列表)
- 六、踩坑提醒
-
- [6.1 Stream只能消费一次](#6.1 Stream只能消费一次)
- [6.2 不要修改源数据](#6.2 不要修改源数据)
- [6.3 注意空指针异常](#6.3 注意空指针异常)
- [6.4 并行流的线程安全问题](#6.4 并行流的线程安全问题)
- 七、面试高频考点
-
- [7.1 Stream和Collection的区别?](#7.1 Stream和Collection的区别?)
- [7.2 Stream的惰性求值是什么意思?](#7.2 Stream的惰性求值是什么意思?)
- [7.3 什么时候使用并行流?](#7.3 什么时候使用并行流?)
- 八、总结
- 参考资料
- 互动话题
一、为什么需要Stream API
1.1 传统集合操作的痛点
在没有Stream API之前,处理集合数据通常需要编写大量的样板代码:
java
import java.util.*;
public class TraditionalApproach {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 需求:获取所有偶数,平方后排序,取前3个
// 传统方式 - 代码冗长、可读性差
List<Integer> evenNumbers = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
evenNumbers.add(num);
}
}
List<Integer> squared = new ArrayList<>();
for (Integer num : evenNumbers) {
squared.add(num * num);
}
Collections.sort(squared);
List<Integer> result = new ArrayList<>();
for (int i = 0; i < Math.min(3, squared.size()); i++) {
result.add(squared.get(i));
}
System.out.println(result); // 输出: [4, 16, 36]
}
}
传统方式的问题:
- 代码冗长:需要多行代码完成简单操作
- 可读性差:业务逻辑被淹没在循环和条件判断中
- 容易出错:需要手动管理索引和临时集合
- 难以并行化:并行处理需要额外的线程管理代码
1.2 Stream API的优势
使用Stream API,同样的需求可以简洁地实现:
java
import java.util.*;
import java.util.stream.Collectors;
public class StreamApproach {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Stream方式 - 声明式、简洁、可读性强
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 筛选偶数
.map(n -> n * n) // 平方
.sorted() // 排序
.limit(3) // 取前3个
.collect(Collectors.toList()); // 收集结果
System.out.println(result); // 输出: [4, 16, 36]
}
}
Stream API的优势:
| 特性 | 传统方式 | Stream API |
|---|---|---|
| 代码风格 | 命令式(How) | 声明式(What) |
| 代码量 | 多 | 少 |
| 可读性 | 差 | 好 |
| 可维护性 | 差 | 好 |
| 并行支持 | 需手动实现 | 一行代码切换 |
| 函数式支持 | 无 | 完整支持 |
1.3 什么是Stream
**Stream(流)**是JDK8中引入的一个新的抽象层,它允许你以声明式的方式处理数据集合。Stream不是数据结构,而是对数据源(集合、数组、I/O等)的抽象视图。
Stream的特点:
- 不存储数据:Stream本身不存储元素,只是对数据源的视图
- 不改变源数据:Stream操作不会修改原始数据源
- 惰性求值:中间操作不会立即执行,直到遇到终止操作
- 一次消费:Stream只能被消费一次,之后需要重新创建
- 支持并行:可以方便地切换串行/并行执行
二、Stream的创建方式
2.1 从集合创建
java
import java.util.*;
import java.util.stream.Stream;
public class CreateFromCollection {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));
// List创建Stream
Stream<String> listStream = list.stream();
Stream<String> listParallelStream = list.parallelStream(); // 并行流
// Set创建Stream
Stream<Integer> setStream = set.stream();
// 使用示例
listStream.forEach(System.out::println);
// 输出: a b c
}
}
2.2 从数组创建
java
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.DoubleStream;
import java.util.stream.Stream;
public class CreateFromArray {
public static void main(String[] args) {
// 对象数组
String[] strArray = {"a", "b", "c"};
Stream<String> strStream = Arrays.stream(strArray);
Stream<String> strStream2 = Stream.of(strArray);
// 基本类型数组 - 使用特化Stream避免装箱
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);
long[] longArray = {1L, 2L, 3L};
LongStream longStream = Arrays.stream(longArray);
double[] doubleArray = {1.0, 2.0, 3.0};
DoubleStream doubleStream = Arrays.stream(doubleArray);
// 使用示例
int sum = intStream.sum();
System.out.println(sum); // 输出: 15
}
}
2.3 使用Stream.of创建
java
import java.util.stream.Stream;
public class CreateWithStreamOf {
public static void main(String[] args) {
// 直接传入元素
Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);
// 传入数组
String[] array = {"x", "y", "z"};
Stream<String> stream3 = Stream.of(array);
// 空Stream
Stream<String> emptyStream = Stream.empty();
// 使用示例
stream1.forEach(System.out::print); // 输出: abc
System.out.println();
System.out.println(emptyStream.count()); // 输出: 0
}
}
2.4 使用Stream.generate和Stream.iterate创建无限流
java
import java.util.stream.Stream;
import java.util.Random;
import java.util.List;
import java.util.stream.Collectors;
public class CreateInfiniteStream {
public static void main(String[] args) {
// Stream.generate - 接受Supplier,生成无限流
Stream<Double> randomStream = Stream.generate(Math::random);
// 取前5个随机数
List<Double> randomNumbers = randomStream
.limit(5)
.collect(Collectors.toList());
System.out.println(randomNumbers);
// 使用Random生成随机数
Random random = new Random();
Stream<Integer> randomIntStream = Stream.generate(random::nextInt);
List<Integer> randomInts = randomIntStream
.limit(5)
.collect(Collectors.toList());
System.out.println(randomInts);
// Stream.iterate - 接受种子和UnaryOperator,生成无限流
// 生成偶数序列: 0, 2, 4, 6, 8, ...
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2);
List<Integer> first5Evens = evenNumbers
.limit(5)
.collect(Collectors.toList());
System.out.println(first5Evens); // 输出: [0, 2, 4, 6, 8]
// JDK9+ 支持带条件的iterate
// Stream.iterate(0, n -> n < 100, n -> n + 2) // n < 100时停止
}
}
2.5 从I/O和其他源创建
java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
import java.io.BufferedReader;
import java.io.StringReader;
public class CreateFromIO {
public static void main(String[] args) {
// 从文件创建Stream(需要处理IOException)
try {
Stream<String> lines = Files.lines(Paths.get("test.txt"));
// 处理文件内容
// lines.forEach(System.out::println);
lines.close();
} catch (IOException e) {
e.printStackTrace();
}
// 从BufferedReader创建Stream
String content = "Line 1\nLine 2\nLine 3";
BufferedReader reader = new BufferedReader(new StringReader(content));
try (Stream<String> lineStream = reader.lines()) {
lineStream.forEach(System.out::println);
}
// 输出:
// Line 1
// Line 2
// Line 3
// 从字符串创建字符Stream
String str = "Hello";
str.chars() // 返回IntStream
.mapToObj(c -> (char) c)
.forEach(System.out::print);
// 输出: Hello
}
}
2.6 创建方式对比表
| 创建方式 | 方法 | 适用场景 | 示例 |
|---|---|---|---|
| 集合 | collection.stream() | 从List/Set等创建 | list.stream() |
| 数组(对象) | Arrays.stream() | 从对象数组创建 | Arrays.stream(arr) |
| 数组(基本类型) | Arrays.stream() | 从基本类型数组创建 | Arrays.stream(intArr) |
| 直接创建 | Stream.of() | 直接传入元素 | Stream.of(1,2,3) |
| 无限流 | Stream.generate() | 生成无限随机/常量流 | Stream.generate(Math::random) |
| 无限流 | Stream.iterate() | 生成有规律无限流 | Stream.iterate(0, n->n+1) |
| 文件 | Files.lines() | 从文件读取行 | Files.lines(path) |
| 字符串 | String.chars() | 字符流 | "abc".chars() |
三、Stream的中间操作
中间操作(Intermediate Operations)返回一个新的Stream,可以链式调用。它们是惰性求值的,不会立即执行。
3.1 filter - 过滤
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FilterOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 筛选偶数
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evens); // 输出: [2, 4, 6, 8, 10]
// 筛选大于5的数
List<Integer> greaterThan5 = numbers.stream()
.filter(n -> n > 5)
.collect(Collectors.toList());
System.out.println(greaterThan5); // 输出: [6, 7, 8, 9, 10]
// 多条件过滤
List<Integer> complexFilter = numbers.stream()
.filter(n -> n > 3 && n < 8)
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(complexFilter); // 输出: [4, 6]
// 对象过滤
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "");
List<String> validNames = names.stream()
.filter(s -> s != null && !s.isEmpty())
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
System.out.println(validNames); // 输出: [Alice, Charlie, David]
}
}
3.2 map - 映射
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MapOperation {
public static void main(String[] args) {
List<String> names = Arrays.asList("alice", "bob", "charlie");
// 字符串转大写
List<String> upperNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperNames); // 输出: [ALICE, BOB, CHARLIE]
// 获取字符串长度
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(nameLengths); // 输出: [5, 3, 7]
// 数字计算
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squares); // 输出: [1, 4, 9, 16, 25]
// 对象转换
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
List<String> personNames = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
System.out.println(personNames); // 输出: [Alice, Bob, Charlie]
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
3.3 flatMap - 扁平化映射
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapOperation {
public static void main(String[] args) {
// 场景1:扁平化嵌套列表
List<List<Integer>> nestedList = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
// 使用map得到的是Stream<List<Integer>>
// 使用flatMap得到的是Stream<Integer>
List<Integer> flatList = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flatList); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 场景2:处理多个单词
List<String> sentences = Arrays.asList(
"Hello World",
"Java Stream API",
"Functional Programming"
);
List<String> words = sentences.stream()
.flatMap(s -> Arrays.stream(s.split(" ")))
.collect(Collectors.toList());
System.out.println(words); // 输出: [Hello, World, Java, Stream, API, Functional, Programming]
// 场景3:对象中的集合属性
List<Department> departments = Arrays.asList(
new Department("IT", Arrays.asList("Alice", "Bob")),
new Department("HR", Arrays.asList("Charlie", "David")),
new Department("Finance", Arrays.asList("Eve"))
);
List<String> allEmployees = departments.stream()
.flatMap(d -> d.getEmployees().stream())
.collect(Collectors.toList());
System.out.println(allEmployees); // 输出: [Alice, Bob, Charlie, David, Eve]
}
}
class Department {
private String name;
private List<String> employees;
public Department(String name, List<String> employees) {
this.name = name;
this.employees = employees;
}
public List<String> getEmployees() { return employees; }
}
3.4 distinct - 去重
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DistinctOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
// 基本类型去重
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNumbers); // 输出: [1, 2, 3, 4]
// 字符串去重
List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana");
List<String> distinctWords = words.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctWords); // 输出: [apple, banana, cherry]
// 对象去重(基于equals和hashCode)
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Alice", 25) // 与第一个相同
);
List<Person> distinctPeople = people.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctPeople.size()); // 输出取决于Person的equals实现
}
}
3.5 sorted - 排序
java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortedOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
// 自然排序(升序)
List<Integer> sorted = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sorted); // 输出: [1, 2, 3, 5, 8, 9]
// 降序排序
List<Integer> reverseSorted = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(reverseSorted); // 输出: [9, 8, 5, 3, 2, 1]
// 自定义排序
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> byLength = names.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
System.out.println(byLength); // 输出: [Bob, Alice, David, Charlie]
// 多级排序
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 30),
new Person("David", 25)
);
List<Person> multiSorted = people.stream()
.sorted(Comparator.comparing(Person::getAge)
.thenComparing(Person::getName))
.collect(Collectors.toList());
multiSorted.forEach(p -> System.out.println(p.getName() + ": " + p.getAge()));
// 输出:
// Bob: 25
// David: 25
// Alice: 30
// Charlie: 30
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
3.6 peek - 查看元素
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PeekOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// peek用于调试,查看流中的元素
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("Original: " + n))
.map(n -> n * n)
.peek(n -> System.out.println("Squared: " + n))
.filter(n -> n > 10)
.peek(n -> System.out.println("Filtered: " + n))
.collect(Collectors.toList());
System.out.println("Result: " + result);
// 输出:
// Original: 1
// Squared: 1
// Original: 2
// Squared: 4
// Original: 3
// Squared: 9
// Original: 4
// Squared: 16
// Filtered: 16
// Original: 5
// Squared: 25
// Filtered: 25
// Result: [16, 25]
// 注意:peek主要用于调试,不要在peek中修改元素状态
}
}
3.7 limit和skip - 截取
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class LimitSkipOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// limit - 取前n个
List<Integer> first3 = numbers.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(first3); // 输出: [1, 2, 3]
// skip - 跳过前n个
List<Integer> skip3 = numbers.stream()
.skip(3)
.collect(Collectors.toList());
System.out.println(skip3); // 输出: [4, 5, 6, 7, 8, 9, 10]
// limit + skip 实现分页
int pageSize = 3;
int pageNumber = 2; // 第2页(从1开始)
List<Integer> page = numbers.stream()
.skip((pageNumber - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
System.out.println("Page " + pageNumber + ": " + page); // 输出: Page 2: [4, 5, 6]
// 从无限流中取有限个
List<Integer> infiniteSample = Stream.iterate(1, n -> n + 1)
.limit(5)
.collect(Collectors.toList());
System.out.println(infiniteSample); // 输出: [1, 2, 3, 4, 5]
}
}
3.8 中间操作对比表
| 操作 | 功能 | 返回值 | 示例 |
|---|---|---|---|
| filter | 过滤元素 | Stream | filter(n -> n > 5) |
| map | 转换元素 | Stream | map(String::toUpperCase) |
| flatMap | 扁平化 | Stream | flatMap(List::stream) |
| distinct | 去重 | Stream | distinct() |
| sorted | 排序 | Stream | sorted() / sorted(Comparator) |
| peek | 查看元素 | Stream | peek(System.out::println) |
| limit | 限制数量 | Stream | limit(10) |
| skip | 跳过元素 | Stream | skip(5) |
四、Stream的终止操作
终止操作(Terminal Operations)会触发实际计算并产生结果。执行终止操作后,Stream就被消费了,不能再次使用。
4.1 forEach - 遍历
java
import java.util.Arrays;
import java.util.List;
public class ForEachOperation {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 基本遍历
names.stream().forEach(System.out::println);
// 输出:
// Alice
// Bob
// Charlie
// 带索引的遍历(需要借助IntStream)
List<String> items = Arrays.asList("a", "b", "c");
int[] index = {0};
items.forEach(item -> {
System.out.println(index[0] + ": " + item);
index[0]++;
});
// 输出:
// 0: a
// 1: b
// 2: c
// 注意:forEach是终止操作,之后不能再链式调用
}
}
4.2 collect - 收集
java
import java.util.*;
import java.util.stream.Collectors;
public class CollectOperation {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 收集到List
List<String> list = names.stream()
.collect(Collectors.toList());
// 收集到Set
Set<String> set = names.stream()
.collect(Collectors.toSet());
// 收集到指定集合类型
LinkedList<String> linkedList = names.stream()
.collect(Collectors.toCollection(LinkedList::new));
// 收集到Map
Map<String, Integer> nameLengthMap = names.stream()
.collect(Collectors.toMap(
name -> name,
String::length
));
System.out.println(nameLengthMap); // 输出: {Bob=3, Alice=5, David=5, Charlie=7}
// 处理重复key的情况
List<String> withDuplicates = Arrays.asList("a", "b", "a");
Map<String, Integer> mapWithMerge = withDuplicates.stream()
.collect(Collectors.toMap(
s -> s,
s -> 1,
(existing, replacement) -> existing + replacement
));
System.out.println(mapWithMerge); // 输出: {a=2, b=1}
// 字符串连接
String joined = names.stream()
.collect(Collectors.joining(", "));
System.out.println(joined); // 输出: Alice, Bob, Charlie, David
// 带前缀后缀的连接
String formatted = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(formatted); // 输出: [Alice, Bob, Charlie, David]
// 分组
Map<Integer, List<String>> groupedByLength = names.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println(groupedByLength); // 输出: {3=[Bob], 5=[Alice, David], 7=[Charlie]}
// 分区(特殊的分组,分为true和false两组)
Map<Boolean, List<String>> partitioned = names.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 4));
System.out.println(partitioned); // 输出: {false=[Bob], true=[Alice, Charlie, David]}
// 统计
IntSummaryStatistics stats = names.stream()
.collect(Collectors.summarizingInt(String::length));
System.out.println("Count: " + stats.getCount()); // 输出: Count: 4
System.out.println("Sum: " + stats.getSum()); // 输出: Sum: 20
System.out.println("Min: " + stats.getMin()); // 输出: Min: 3
System.out.println("Max: " + stats.getMax()); // 输出: Max: 7
System.out.println("Average: " + stats.getAverage()); // 输出: Average: 5.0
}
}
4.3 toArray - 转换为数组
java
import java.util.Arrays;
import java.util.List;
public class ToArrayOperation {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 转换为Object数组
Object[] objectArray = names.stream().toArray();
System.out.println(Arrays.toString(objectArray)); // 输出: [Alice, Bob, Charlie]
// 转换为指定类型数组
String[] stringArray = names.stream()
.toArray(String[]::new);
System.out.println(Arrays.toString(stringArray)); // 输出: [Alice, Bob, Charlie]
// 基本类型数组(使用IntStream等特化流)
int[] intArray = names.stream()
.mapToInt(String::length)
.toArray();
System.out.println(Arrays.toString(intArray)); // 输出: [5, 3, 7]
}
}
4.4 reduce - 归约
java
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class ReduceOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和(带初始值)
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum); // 输出: Sum: 15
// 求和(方法引用)
int sum2 = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("Sum2: " + sum2); // 输出: Sum2: 15
// 求和(不带初始值,返回Optional)
Optional<Integer> sumOptional = numbers.stream()
.reduce((a, b) -> a + b);
sumOptional.ifPresent(s -> System.out.println("Sum Optional: " + s));
// 乘法
int product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println("Product: " + product); // 输出: Product: 120
// 字符串连接
List<String> words = Arrays.asList("Hello", " ", "World", "!");
String sentence = words.stream()
.reduce("", String::concat);
System.out.println(sentence); // 输出: Hello World!
// 找最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
max.ifPresent(m -> System.out.println("Max: " + m)); // 输出: Max: 5
// 找最小值
Optional<Integer> min = numbers.stream()
.reduce(Integer::min);
min.ifPresent(m -> System.out.println("Min: " + m)); // 输出: Min: 1
// 复杂归约:拼接带分隔符的字符串
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String joined = names.stream()
.reduce((a, b) -> a + ", " + b)
.orElse("");
System.out.println(joined); // 输出: Alice, Bob, Charlie
}
}
4.5 min/max/count - 统计
java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class MinMaxCountOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
// count - 计数
long count = numbers.stream().count();
System.out.println("Count: " + count); // 输出: Count: 6
// max - 最大值
Optional<Integer> max = numbers.stream()
.max(Integer::compareTo);
max.ifPresent(m -> System.out.println("Max: " + m)); // 输出: Max: 9
// min - 最小值
Optional<Integer> min = numbers.stream()
.min(Integer::compareTo);
min.ifPresent(m -> System.out.println("Min: " + m)); // 输出: Min: 1
// 使用自定义比较器
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 按长度找最长
Optional<String> longest = names.stream()
.max(Comparator.comparing(String::length));
longest.ifPresent(s -> System.out.println("Longest: " + s)); // 输出: Longest: Charlie
// 按长度找最短
Optional<String> shortest = names.stream()
.min(Comparator.comparing(String::length));
shortest.ifPresent(s -> System.out.println("Shortest: " + s)); // 输出: Shortest: Bob
// 按字母顺序
Optional<String> firstAlphabetically = names.stream()
.min(String::compareTo);
firstAlphabetically.ifPresent(s -> System.out.println("First: " + s)); // 输出: First: Alice
}
}
4.6 匹配操作
java
import java.util.Arrays;
import java.util.List;
public class MatchOperation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
// anyMatch - 是否有任意一个匹配
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0);
System.out.println("Has even: " + hasEven); // 输出: Has even: true
boolean hasGreaterThan5 = numbers.stream()
.anyMatch(n -> n > 5);
System.out.println("Has > 5: " + hasGreaterThan5); // 输出: Has > 5: true
// allMatch - 是否全部匹配
boolean allEven = numbers.stream()
.allMatch(n -> n % 2 == 0);
System.out.println("All even: " + allEven); // 输出: All even: true
boolean allGreaterThan5 = numbers.stream()
.allMatch(n -> n > 5);
System.out.println("All > 5: " + allGreaterThan5); // 输出: All > 5: false
// noneMatch - 是否全部不匹配
boolean noOdd = numbers.stream()
.noneMatch(n -> n % 2 != 0);
System.out.println("No odd: " + noOdd); // 输出: No odd: true
// 空流的匹配
List<Integer> empty = Arrays.asList();
System.out.println("Empty anyMatch: " + empty.stream().anyMatch(n -> n > 0)); // false
System.out.println("Empty allMatch: " + empty.stream().allMatch(n -> n > 0)); // true
System.out.println("Empty noneMatch: " + empty.stream().noneMatch(n -> n > 0)); // true
}
}
4.7 findFirst/findAny - 查找
java
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class FindOperation {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// findFirst - 返回第一个元素(串行流中确定性)
Optional<String> first = names.stream()
.findFirst();
first.ifPresent(s -> System.out.println("First: " + s)); // 输出: First: Alice
// findAny - 返回任意一个元素(并行流中更高效)
Optional<String> any = names.stream()
.findAny();
any.ifPresent(s -> System.out.println("Any: " + s)); // 输出: Any: Alice(串行时通常是第一个)
// 结合filter使用
Optional<String> firstWithC = names.stream()
.filter(s -> s.startsWith("C"))
.findFirst();
firstWithC.ifPresent(s -> System.out.println("First with C: " + s)); // 输出: First with C: Charlie
// 找不到时返回empty Optional
Optional<String> firstWithZ = names.stream()
.filter(s -> s.startsWith("Z"))
.findFirst();
System.out.println("First with Z present: " + firstWithZ.isPresent()); // 输出: false
}
}
4.8 终止操作对比表
| 操作 | 功能 | 返回值 | 示例 |
|---|---|---|---|
| forEach | 遍历元素 | void | forEach(System.out::println) |
| collect | 收集到集合 | 集合 | collect(Collectors.toList()) |
| toArray | 转换为数组 | 数组 | toArray(String[]::new) |
| reduce | 归约 | Optional/值 | reduce(0, Integer::sum) |
| count | 计数 | long | count() |
| min | 最小值 | Optional | min(Comparator) |
| max | 最大值 | Optional | max(Comparator) |
| anyMatch | 任意匹配 | boolean | anyMatch(Predicate) |
| allMatch | 全部匹配 | boolean | allMatch(Predicate) |
| noneMatch | 全部不匹配 | boolean | noneMatch(Predicate) |
| findFirst | 找第一个 | Optional | findFirst() |
| findAny | 找任意一个 | Optional | findAny() |
五、经验之谈:Stream操作是惰性求值的
5.1 什么是惰性求值
Stream的中间操作是**惰性(Lazy)**的,它们不会立即执行,而是等到终止操作被调用时才会一并执行。这种设计带来了性能优化:
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LazyEvaluation {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println("开始创建Stream...");
var stream = numbers.stream()
.peek(n -> System.out.println("peek: " + n)) // 中间操作
.filter(n -> {
System.out.println("filter: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("map: " + n);
return n * n;
});
System.out.println("Stream创建完成,尚未执行任何操作");
System.out.println("---");
// 直到调用终止操作,中间操作才会执行
System.out.println("调用终止操作...");
List<Integer> result = stream.limit(3).collect(Collectors.toList());
System.out.println("结果: " + result);
// 注意:由于limit(3)的存在,Stream在处理完3个元素后就停止了
// 这就是短路操作带来的优化
}
}
5.2 短路操作的优势
某些中间操作是**短路(Short-circuiting)**的,它们可以在满足条件时提前终止处理:
java
import java.util.stream.Stream;
import java.util.List;
import java.util.stream.Collectors;
public class ShortCircuiting {
public static void main(String[] args) {
// 无限流 + limit = 安全的操作
List<Integer> first10 = Stream.iterate(1, n -> n + 1)
.peek(n -> System.out.println("生成: " + n))
.limit(10)
.collect(Collectors.toList());
System.out.println("前10个: " + first10);
// 只生成了10个元素,不会无限循环
// findFirst也是短路操作
Integer firstEven = Stream.iterate(1, n -> n + 1)
.peek(n -> System.out.println("检查: " + n))
.filter(n -> n % 2 == 0)
.findFirst()
.orElse(0);
System.out.println("第一个偶数: " + firstEven);
// 只检查了1和2就返回了
}
}
5.3 短路操作列表
| 操作类型 | 操作 | 说明 |
|---|---|---|
| 中间操作 | limit | 限制元素数量 |
| 中间操作 | skip | 跳过元素 |
| 终止操作 | findFirst | 找到第一个就停止 |
| 终止操作 | findAny | 找到任意一个就停止 |
| 终止操作 | anyMatch | 有一个匹配就停止 |
| 终止操作 | allMatch | 有一个不匹配就停止 |
| 终止操作 | noneMatch | 有一个匹配就停止 |
六、踩坑提醒
6.1 Stream只能消费一次
坑点:Stream被终止操作消费后,不能再次使用。
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamConsumedPitfall {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
java.util.stream.Stream<String> stream = names.stream();
// 第一次使用
List<String> list1 = stream.collect(Collectors.toList());
System.out.println(list1);
// 错误:Stream已经被消费,再次使用会抛异常
// List<String> list2 = stream.collect(Collectors.toList());
// 抛出: java.lang.IllegalStateException: stream has already been operated upon or closed
// 正确做法:重新创建Stream
List<String> list2 = names.stream().collect(Collectors.toList());
System.out.println(list2);
}
}
6.2 不要修改源数据
坑点:Stream操作不应修改源数据,这会导致不可预期的结果,特别是在并行流中。
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ModifySourcePitfall {
public static void main(String[] args) {
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
// 错误:在Stream操作中修改源数据
// names.stream().forEach(names::remove); // 会抛出ConcurrentModificationException
// 错误:使用filter后修改原集合
List<String> filtered = names.stream()
.filter(s -> s.length() > 3)
.collect(java.util.stream.Collectors.toList());
names.clear(); // 这会影响到后续可能使用的filtered吗?不会,但容易混淆
// 正确做法:Stream操作不修改源数据,只产生新数据
List<String> upperNames = names.stream()
.map(String::toUpperCase) // 创建新字符串,不修改原字符串
.collect(java.util.stream.Collectors.toList());
System.out.println("原始: " + names); // 输出: 原始: []
System.out.println("转换后: " + upperNames); // 输出: 转换后: []
}
}
6.3 注意空指针异常
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class NullPointerPitfall {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", null, "Bob", null, "Charlie");
// 错误:直接操作可能包含null的Stream
// List<Integer> lengths = names.stream()
// .map(String::length) // 会抛出NullPointerException
// .collect(Collectors.toList());
// 正确做法:先过滤null
List<Integer> lengths = names.stream()
.filter(s -> s != null)
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); // 输出: [5, 3, 7]
// 或者使用Objects.nonNull
List<String> nonNullNames = names.stream()
.filter(java.util.Objects::nonNull)
.collect(Collectors.toList());
System.out.println(nonNullNames); // 输出: [Alice, Bob, Charlie]
}
}
6.4 并行流的线程安全问题
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
public class ParallelStreamPitfall {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 错误:在并行流中使用非线程安全的集合
List<Integer> result = new ArrayList<>();
numbers.parallelStream()
.forEach(result::add); // 可能丢数据或抛异常
System.out.println("结果数量: " + result.size()); // 可能少于10
// 正确做法1:使用collect合并结果
List<Integer> result2 = numbers.parallelStream()
.collect(Collectors.toList());
System.out.println("结果2数量: " + result2.size()); // 输出: 10
// 正确做法2:使用线程安全的集合(性能较差)
List<Integer> result3 = new CopyOnWriteArrayList<>();
numbers.parallelStream()
.forEach(result3::add);
System.out.println("结果3数量: " + result3.size()); // 输出: 10
}
}
七、面试高频考点
7.1 Stream和Collection的区别?
| 特性 | Collection | Stream |
|---|---|---|
| 存储 | 存储数据元素 | 不存储数据,是计算视图 |
| 遍历 | 外部迭代(for/iterator) | 内部迭代 |
| 使用次数 | 可重复使用 | 只能消费一次 |
| 修改 | 可以修改元素 | 不修改源数据 |
| 执行方式 | 立即执行 | 惰性求值 |
| 并行支持 | 需手动实现 | 一行代码切换 |
| 适用场景 | 数据存储 | 数据计算 |
7.2 Stream的惰性求值是什么意思?
**惰性求值(Lazy Evaluation)**是指Stream的中间操作不会立即执行,而是被记录下来,等到终止操作被调用时才一并执行。
好处:
- 性能优化:可以合并多个操作,减少遍历次数
- 短路优化:可以在满足条件时提前终止,避免不必要的计算
- 支持无限流:可以处理理论上无限的数据源
java
list.stream()
.filter(...) // 不执行
.map(...) // 不执行
.sorted(...) // 不执行
.collect(...); // 此时所有操作一起执行
7.3 什么时候使用并行流?
适合使用并行流的情况:
- 数据量大(通常 > 10,000 个元素)
- 计算复杂(每个元素的处理耗时较长)
- 无状态、无副作用的操作
- 数据源可高效分割(如ArrayList、数组、IntStream.range)
不适合使用并行流的情况:
- 数据量小(线程切换开销 > 并行收益)
- 需要保证顺序(虽然可以ordered(),但有性能损耗)
- 涉及I/O操作(阻塞common pool)
- 需要修改共享变量
- 数据源分割效率低(如LinkedList、Stream.iterate)
八、总结
今天我们深入学习了JDK8的Stream API(上篇),主要内容包括:
- 为什么需要Stream API:解决了传统集合操作代码冗长、可读性差的问题
- Stream的创建方式:从集合、数组、Stream.of、generate/iterate等多种方式
- 中间操作:filter、map、flatMap、distinct、sorted、peek、limit、skip
- 终止操作:forEach、collect、toArray、reduce、min/max/count、match、find
- 惰性求值:中间操作延迟执行,带来性能优化
- 常见坑点:Stream只能消费一次、不要修改源数据、注意线程安全
下一步预告
在Day5中,我们将继续学习Stream API(下),内容包括:
- 数值特化流(IntStream、LongStream、DoubleStream)
- 高级Collectors用法
- 并行流的原理与最佳实践
- Stream的性能优化技巧
敬请期待!
参考资料
互动话题
- 你在项目中使用Stream API了吗?最常用哪些操作?
- 有没有因为Stream的惰性求值特性而踩过坑?
- 你觉得Stream API让代码更易读还是更难理解了?
欢迎在评论区分享你的经验和看法,点赞收藏不迷路,我们Day5见!