文章目录
一、Stream流概述
Java 8引入的Stream API是处理集合数据的革命性工具,它允许我们以声明式方式处理数据,并支持并行操作。Stream不是数据结构,而是对数据源(集合、数组等)的高级抽象,可以让我们写出更简洁、更易读的代码。
Stream与传统集合操作对比
- 内部迭代:Stream使用内部迭代(由库控制),而集合使用外部迭代(程序员控制)
- 惰性求值:许多Stream操作是惰性的,可以提高性能
- 函数式风格:支持lambda表达式和方法引用
- 可并行:只需调用parallel()就能实现并行处理
二、Stream创建方式
1. 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 顺序流
Stream<String> parallelStream = list.parallelStream(); // 并行流
2. 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
3. 使用Stream.of()
Stream<String> stream = Stream.of("a", "b", "c");
4. 使用Stream.generate()创建无限流
Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
randomStream.forEach(System.out::println);
5. 使用Stream.iterate()创建无限流
Stream<Integer> evenNumStream = Stream.iterate(0, n -> n + 2).limit(5);
evenNumStream.forEach(System.out::println);
6. 其他创建方式
// 空流
Stream<String> emptyStream = Stream.empty();
// 基本类型流
IntStream intStream = IntStream.range(1, 5); // 1,2,3,4
LongStream longStream = LongStream.rangeClosed(1, 5); // 1,2,3,4,5
三、Stream中间操作
中间操作返回一个新的Stream,可以进行链式调用。
1. filter() - 过滤
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 结果: [2, 4, 6]
2. map() - 映射
List<String> words = Arrays.asList("hello", "world");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
// 结果: [5, 5]
3. flatMap() - 扁平化映射
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
List<String> flatList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 结果: ["a", "b", "c", "d"]
4. distinct() - 去重
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
// 结果: [1, 2, 3]
5. sorted() - 排序
List<String> words = Arrays.asList("banana", "apple", "pear");
List<String> sortedWords = words.stream()
.sorted()
.collect(Collectors.toList());
// 结果: ["apple", "banana", "pear"]
// 自定义排序
List<String> customSorted = words.stream()
.sorted(Comparator.comparing(String::length).reversed())
.collect(Collectors.toList());
// 结果: ["banana", "apple", "pear"]
6. limit()和skip() - 截取和跳过
List<Integer> numbers = IntStream.range(1, 10).boxed().collect(Collectors.toList());
List<Integer> limited = numbers.stream().limit(5).collect(Collectors.toList());
// 结果: [1, 2, 3, 4, 5]
List<Integer> skipped = numbers.stream().skip(5).collect(Collectors.toList());
// 结果: [6, 7, 8, 9]
7. peek() - 查看流中元素
List<String> result = Stream.of("one", "two", "three")
.filter(s -> s.length() > 3)
.peek(s -> System.out.println("Filtered value: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("Mapped value: " + s))
.collect(Collectors.toList());
四、Stream终端操作
终端操作会消耗流,产生一个结果或副作用。
1. forEach() - 遍历
List<String> words = Arrays.asList("hello", "world");
words.stream().forEach(System.out::println);
2. collect() - 收集为集合
List<String> collected = Stream.of("a", "b", "c")
.collect(Collectors.toList());
// 转为Set
Set<String> set = Stream.of("a", "b", "a")
.collect(Collectors.toSet());
// 结果: ["a", "b"]
// 转为Map
Map<String, Integer> map = Stream.of("apple", "banana", "pear")
.collect(Collectors.toMap(
Function.identity(), // key
String::length // value
));
// 结果: {apple=5, banana=6, pear=4}
3. reduce() - 归约
// 求和
Optional<Integer> sum = Stream.of(1, 2, 3, 4).reduce(Integer::sum);
// 结果: Optional[10]
// 使用初始值
Integer sumWithIdentity = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 结果: 10
// 字符串连接
String concatenated = Stream.of("a", "b", "c").reduce("", String::concat);
// 结果: "abc"
4. min()/max() - 最小/最大值
Optional<String> min = Stream.of("banana", "apple", "pear")
.min(Comparator.naturalOrder());
// 结果: Optional["apple"]
Optional<Integer> max = Stream.of(1, 2, 3, 4).max(Integer::compare);
// 结果: Optional[4]
5. count() - 计数
long count = Stream.of("a", "b", "c").count();
// 结果: 3
6. anyMatch()/allMatch()/noneMatch() - 匹配检查
boolean anyStartsWithA = Stream.of("apple", "banana", "pear")
.anyMatch(s -> s.startsWith("a"));
// 结果: true
boolean allLongerThan3 = Stream.of("apple", "banana", "pear")
.allMatch(s -> s.length() > 3);
// 结果: true
boolean noneStartsWithZ = Stream.of("apple", "banana", "pear")
.noneMatch(s -> s.startsWith("z"));
// 结果: true
7. findFirst()/findAny() - 查找元素
Optional<String> first = Stream.of("apple", "banana", "pear")
.findFirst();
// 结果: Optional["apple"]
Optional<String> any = Stream.of("apple", "banana", "pear")
.parallel() // 并行流中findAny可能更快
.findAny();
// 结果可能是Optional["apple"], Optional["banana"]或Optional["pear"]
五、数值流特化
Java 8引入了三种原始类型特化流:IntStream、LongStream和DoubleStream,避免装箱拆箱开销。
1. 创建数值流
IntStream intStream = IntStream.range(1, 5); // 1,2,3,4
IntStream closedIntStream = IntStream.rangeClosed(1, 5); // 1,2,3,4,5
// 将对象流转为数值流
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
IntStream intStreamFromObj = numbers.stream().mapToInt(Integer::intValue);
2. 常用数值流操作
// 求和
int sum = IntStream.range(1, 5).sum(); // 10
// 平均值
OptionalDouble avg = IntStream.range(1, 5).average(); // OptionalDouble[2.5]
// 最大值
OptionalInt max = IntStream.range(1, 5).max(); // OptionalInt[4]
// 统计信息
IntSummaryStatistics stats = IntStream.range(1, 5).summaryStatistics();
// stats: IntSummaryStatistics{count=4, sum=10, min=1, average=2.5, max=4}
3. 数值流转回对象流
Stream<Integer> boxed = IntStream.range(1, 5).boxed();
六、并行流
Stream API可以轻松实现并行处理。
1. 创建并行流
// 方式1: 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> parallelStream = list.parallelStream();
// 方式2: 将顺序流转为并行流
Stream<String> parallel = Stream.of("a", "b", "c").parallel();
2. 并行流注意事项
// 错误示例: 共享可变状态
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> badParallelResult = new ArrayList<>();
numbers.parallelStream()
.map(n -> n * 2)
.forEach(badParallelResult::add); // 可能抛出ArrayIndexOutOfBoundsException
// 正确做法: 使用线程安全的收集器
List<Integer> goodParallelResult = numbers.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList());
3. 并行流性能考虑
- 数据量大时(通常超过1万元素)才考虑使用并行流
- 考虑操作本身的并行成本(如NQ模型)
- 避免在并行流中使用有状态的操作(如sorted())
- 注意线程安全问题
七、高级收集器
Collectors类提供了丰富的收集器实现。
1. 分组
class Person {
String name;
String city;
int age;
// 构造方法、getter/setter省略
}
List<Person> people = Arrays.asList(
new Person("Alice", "New York", 25),
new Person("Bob", "New York", 30),
new Person("Charlie", "London", 28)
);
// 简单分组
Map<String, List<Person>> peopleByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
// 结果: {"New York":[Alice, Bob], "London":[Charlie]}
// 多级分组
Map<String, Map<Integer, List<Person>>> peopleByCityAndAge = people.stream()
.collect(Collectors.groupingBy(Person::getCity,
Collectors.groupingBy(Person::getAge)));
2. 分区
// 分区是分组的特殊情况,谓词只有true/false两种情况
Map<Boolean, List<Person>> partitioned = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() > 27));
// 结果: {false=[Alice], true=[Bob, Charlie]}
3. 连接字符串
String joined = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", "));
// 结果: "Alice, Bob, Charlie"
4. 汇总统计
IntSummaryStatistics ageStats = people.stream()
.collect(Collectors.summarizingInt(Person::getAge));
// 结果: IntSummaryStatistics{count=3, sum=83, min=25, average=27.666667, max=30}
5. 自定义收集器
// 实现Collector接口可以创建自定义收集器
Collector<Person, ?, TreeSet<Person>> toTreeSet = Collector.of(
TreeSet::new,
TreeSet::add,
(left, right) -> { left.addAll(right); return left; },
Collector.Characteristics.UNORDERED
);
TreeSet<Person> personTreeSet = people.stream().collect(toTreeSet);
八、实战案例
案例1:统计单词频率
String sentence = "hello world hello java world stream";
Map<String, Long> wordCount = Arrays.stream(sentence.split(" "))
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
// 结果: {hello=2, world=2, java=1, stream=1}
案例2:找出最贵的商品
class Product {
String name;
double price;
// 构造方法、getter/setter省略
}
List<Product> products = Arrays.asList(
new Product("Laptop", 999.99),
new Product("Phone", 699.99),
new Product("Tablet", 499.99)
);
Optional<Product> mostExpensive = products.stream()
.max(Comparator.comparing(Product::getPrice));
// 结果: Optional[Product{name='Laptop', price=999.99}]
案例3:数据转换和处理
List<String> transactions = Arrays.asList(
"1001,2023-01-01,150.00",
"1002,2023-01-02,75.50",
"1003,2023-01-01,225.75"
);
// 解析并计算1月1日的总交易额
double totalJan1 = transactions.stream()
.map(t -> t.split(","))
.filter(parts -> parts[1].equals("2023-01-01"))
.mapToDouble(parts -> Double.parseDouble(parts[2]))
.sum();
// 结果: 375.75
九、Stream性能优化
- 避免装箱拆箱:使用原始类型特化流(IntStream等)
- 短路操作:尽早使用filter()减少后续处理量
- 顺序与并行选择:根据数据量和操作复杂度选择
- 避免副作用:保持lambda表达式的纯粹性
- 重用流:流一旦被消费就不能重用,需要重新创建
十、总结
Java Stream API提供了一种高效、声明式处理数据的方式。通过本文的学习,你应该已经掌握了:
- Stream的创建方式
- 丰富的中间操作和终端操作
- 并行流的使用和注意事项
- 强大的收集器功能
- 实际应用场景和性能优化技巧
Stream不仅让代码更简洁,还能利用多核处理器提高性能。但要注意,不是所有场景都适合使用Stream,应根据具体情况选择最合适的工具。
附录:常见问题解答
Q: Stream和Collection有什么区别?
A: Collection主要关注数据存储,而Stream关注数据处理。Collection是内存中的数据结构,Stream不是。
Q: 什么时候应该使用并行流?
A: 当处理大量数据且操作可以并行化时,且需要考虑并行带来的开销是否值得。
Q: Stream可以重用吗?
A: 不可以。Stream一旦被消费(终端操作执行)就不能再使用。
Q: Stream会修改源数据吗?
A: 不会。Stream操作不会修改源数据,而是产生新的Stream或结果。