文章目录
- 一、引言
-
- [1.1 什么是Java Stream](#1.1 什么是Java Stream)
- [1.2 Stream的优势](#1.2 Stream的优势)
- [1.3 应用场景](#1.3 应用场景)
- 二、Stream创建方法
-
- [2.1 从Collection创建](#2.1 从Collection创建)
- [2.2 从数组创建](#2.2 从数组创建)
- [2.3 从值创建](#2.3 从值创建)
- [2.4 其他创建方式](#2.4 其他创建方式)
- 三、中间操作方法
-
- [3.1 筛选与切片](#3.1 筛选与切片)
-
- [filter() - 过滤元素](#filter() - 过滤元素)
- [distinct() - 去重](#distinct() - 去重)
- [limit() - 限制元素数量](#limit() - 限制元素数量)
- [skip() - 跳过元素](#skip() - 跳过元素)
- [3.2 映射](#3.2 映射)
-
- [map() - 元素转换](#map() - 元素转换)
- [flatMap() - 扁平化映射](#flatMap() - 扁平化映射)
- [3.3 排序](#3.3 排序)
-
- [sorted() - 自然排序](#sorted() - 自然排序)
- [sorted(Comparator) - 自定义排序](#sorted(Comparator) - 自定义排序)
- [3.4 其他中间操作](#3.4 其他中间操作)
-
- [peek() - 查看元素](#peek() - 查看元素)
- 四、终端操作方法
-
- [4.1 查找与匹配](#4.1 查找与匹配)
-
- [allMatch() - 全部匹配](#allMatch() - 全部匹配)
- [anyMatch() - 任意匹配](#anyMatch() - 任意匹配)
- [noneMatch() - 无匹配](#noneMatch() - 无匹配)
- [findFirst() - 查找第一个](#findFirst() - 查找第一个)
- [findAny() - 查找任意一个](#findAny() - 查找任意一个)
- [4.2 归约](#4.2 归约)
-
- [reduce() - 归约操作](#reduce() - 归约操作)
- [4.3 收集](#4.3 收集)
-
- [collect() - 收集结果](#collect() - 收集结果)
- Collectors常用方法
-
- [toList() - 收集到List](#toList() - 收集到List)
- [toSet() - 收集到Set](#toSet() - 收集到Set)
- [toCollection() - 收集到指定集合](#toCollection() - 收集到指定集合)
- [toMap() - 收集到Map](#toMap() - 收集到Map)
- [joining() - 字符串连接](#joining() - 字符串连接)
- [groupingBy() - 分组](#groupingBy() - 分组)
- [partitioningBy() - 分区](#partitioningBy() - 分区)
- [mapping() - 映射后收集](#mapping() - 映射后收集)
- [4.4 统计](#4.4 统计)
-
- [count() - 计数](#count() - 计数)
- [max() - 最大值](#max() - 最大值)
- [min() - 最小值](#min() - 最小值)
- [average() - 平均值(IntStream/LongStream/DoubleStream)](#average() - 平均值(IntStream/LongStream/DoubleStream))
- [sum() - 求和(IntStream/LongStream/DoubleStream)](#sum() - 求和(IntStream/LongStream/DoubleStream))
- [summarizingInt/Long/Double - 综合统计](#summarizingInt/Long/Double - 综合统计)
- 五、实际应用案例
- 六、注意事项与最佳实践
-
- [6.1 Stream的特点与限制](#6.1 Stream的特点与限制)
- [6.2 性能考量](#6.2 性能考量)
- [6.3 常见陷阱](#6.3 常见陷阱)
- [6.4 最佳实践总结](#6.4 最佳实践总结)
- 七、总结
-
- [7.1 Stream API的核心价值](#7.1 Stream API的核心价值)
- [7.2 常用方法速查表](#7.2 常用方法速查表)
- [7.3 结语](#7.3 结语)
一、引言
1.1 什么是Java Stream
Java Stream API 是 Java 8 引入的一个重要特性,它提供了一种声明式的方式来处理集合数据。Stream(流)是对集合(Collection)对象功能的增强,专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作(bulk data operation)。
核心特点:
- 声明式编程:使用类似 SQL 查询的语法风格表达数据处理逻辑
- 可组合性:可以将多个操作串联起来形成流水线
- 可并行化:支持并行处理,充分利用多核 CPU 的优势
- 惰性求值:中间操作是惰性的,只有在终端操作时才会执行
- 内部迭代:由 Stream API 内部处理迭代逻辑,代码更简洁
1.2 Stream的优势
| 特性 | 传统方式 | Stream方式 |
|---|---|---|
| 代码简洁度 | 需要大量循环和条件判断 | 链式调用,代码更简洁 |
| 可读性 | 需要深入代码逻辑才能理解 | 声明式描述,语义清晰 |
| 并行处理 | 需要手动编写线程代码 | 简单调用 parallel() 即可 |
| 性能优化 | 需要手动优化 | 底层自动优化 |
1.3 应用场景
- 数据过滤与转换:从集合中筛选、转换数据
- 统计聚合:计算总和、平均值、最大值、最小值等
- 分组与分区:将数据按条件分组或分区
- 数据转换:将一种数据结构转换为另一种
- 复杂查询:类似数据库查询的数据处理
二、Stream创建方法
2.1 从Collection创建
java
// 从List创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> streamFromList = list.stream();
// 从Set创建
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));
Stream<Integer> streamFromSet = set.stream();
// 并行流
Stream<String> parallelStream = list.parallelStream();
方法签名:
java
default Stream<E> stream()
default Stream<E> parallelStream()
2.2 从数组创建
java
// 从数组创建
String[] array = {"Java", "Python", "Go"};
Stream<String> streamFromArray = Arrays.stream(array);
// 指定范围创建流
Stream<String> partialStream = Arrays.stream(array, 0, 2);
// 使用Stream.of()
Stream<String> streamOf = Stream.of("Hello", "World", "Stream");
// 创建空流
Stream<String> emptyStream = Stream.empty();
方法签名:
java
public static <T> Stream<T> stream(T[] array)
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
public static<T> Stream<T> of(T... values)
public static<T> Stream<T> empty()
2.3 从值创建
java
// 使用of()方法
Stream<String> valueStream = Stream.of("Java", "Stream", "API");
// 使用generate()生成无限流
Stream<Double> randomStream = Stream.generate(Math::random);
// 使用iterate()生成无限流
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2);
方法签名:
java
public static<T> Stream<T> generate(Supplier<T> s)
public static<T> Stream<T> iterate(T seed, UnaryOperator<T> f)
2.4 其他创建方式
java
// 从文件创建
try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
// 使用Pattern分割字符串创建流
String text = "hello,world,java";
Stream<String> patternStream = Pattern.compile(",").splitAsStream(text);
// 使用IntStream、LongStream、DoubleStream等基本类型流
IntStream intStream = IntStream.range(1, 10);
LongStream longStream = LongStream.rangeClosed(1, 10);
DoubleStream doubleStream = DoubleStream.of(1.1, 2.2, 3.3);
三、中间操作方法
中间操作返回一个新的Stream,可以串联多个中间操作形成流水线。
3.1 筛选与切片
filter() - 过滤元素
方法签名:
java
Stream<T> filter(Predicate<? super T> predicate)
功能说明: 根据给定的条件过滤流中的元素,保留满足条件的元素。
参数解释:
predicate:断言函数,用于测试每个元素是否满足条件
返回值: 返回一个新的Stream,只包含满足条件的元素
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 过滤偶数
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("偶数: " + evenNumbers);
// 过滤长度大于3的字符串
List<String> words = Arrays.asList("Java", "Python", "Go", "JavaScript");
List<String> longWords = words.stream()
.filter(word -> word.length() > 3)
.collect(Collectors.toList());
System.out.println("长单词: " + longWords);
运行结果:
偶数: [2, 4, 6, 8, 10]
长单词: [Java, Python, JavaScript]
distinct() - 去重
方法签名:
java
Stream<T> distinct()
功能说明: 去除流中重复的元素(基于对象的equals()方法)
参数解释: 无参数
返回值: 返回一个新的Stream,不包含重复元素
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("去重后: " + distinctNumbers);
// 对对象去重(需要重写equals和hashCode)
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
List<Person> people = Arrays.asList(
new Person("张三", 25),
new Person("李四", 30),
new Person("张三", 25),
new Person("王五", 28)
);
List<Person> distinctPeople = people.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("去重后的人员: " + distinctPeople);
运行结果:
去重后: [1, 2, 3, 4, 5]
去重后的人员: [张三(25), 李四(30), 王五(28)]
limit() - 限制元素数量
方法签名:
java
Stream<T> limit(long maxSize)
功能说明: 截取流的前N个元素
参数解释:
maxSize:要返回的元素数量
返回值: 返回一个新的Stream,最多包含maxSize个元素
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 获取前5个元素
List<Integer> firstFive = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println("前5个元素: " + firstFive);
// 先过滤再限制
List<Integer> firstThreeEven = numbers.stream()
.filter(n -> n % 2 == 0)
.limit(3)
.collect(Collectors.toList());
System.out.println("前3个偶数: " + firstThreeEven);
运行结果:
前5个元素: [1, 2, 3, 4, 5]
前3个偶数: [2, 4, 6]
skip() - 跳过元素
方法签名:
java
Stream<T> skip(long n)
功能说明: 跳过流的前N个元素
参数解释:
n:要跳过的元素数量
返回值: 返回一个新的Stream,丢弃前n个元素后的剩余元素
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 跳过前5个元素
List<Integer> afterFive = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println("跳过前5个后: " + afterFive);
// 获取第4到第7个元素
List<Integer> middleElements = numbers.stream()
.skip(3)
.limit(4)
.collect(Collectors.toList());
System.out.println("第4到第7个元素: " + middleElements);
运行结果:
跳过前5个后: [6, 7, 8, 9, 10]
第4到第7个元素: [4, 5, 6, 7]
3.2 映射
map() - 元素转换
方法签名:
java
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
功能说明: 将流中的每个元素转换为另一种形式
参数解释:
mapper:转换函数,接收一个元素,返回转换后的元素
返回值: 返回一个新的Stream,包含转换后的元素
代码示例:
java
// 将字符串转换为大写
List<String> words = Arrays.asList("java", "python", "go");
List<String> upperWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("大写字母: " + upperWords);
// 将对象提取属性
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
}
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
List<String> names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println("员工姓名: " + names);
// 计算平方
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);
运行结果:
大写字母: [JAVA, PYTHON, GO]
员工姓名: [张三, 李四, 王五]
平方数: [1, 4, 9, 16, 25]
flatMap() - 扁平化映射
方法签名:
java
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
功能说明: 将流中的每个元素转换为一个流,然后将所有流连接成一个流
参数解释:
mapper:转换函数,接收一个元素,返回一个Stream
返回值: 返回一个新的Stream,包含所有子流的元素
代码示例:
java
// 将嵌套集合展平
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d", "e"),
Arrays.asList("f")
);
List<String> flattened = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("展平后: " + flattened);
// 将字符串的字符拆分成流
List<String> words = Arrays.asList("Hello", "World");
List<String> characters = words.stream()
.flatMap(word -> word.chars().mapToObj(c -> String.valueOf((char) c)))
.collect(Collectors.toList());
System.out.println("字符列表: " + characters);
// 获取所有员工的所有技能
class Employee {
String name;
List<String> skills;
Employee(String name, List<String> skills) {
this.name = name;
this.skills = skills;
}
public List<String> getSkills() { return skills; }
}
List<Employee> employees = Arrays.asList(
new Employee("张三", Arrays.asList("Java", "Python")),
new Employee("李四", Arrays.asList("Go", "Rust", "C++")),
new Employee("王五", Arrays.asList("JavaScript"))
);
List<String> allSkills = employees.stream()
.flatMap(emp -> emp.getSkills().stream())
.distinct()
.collect(Collectors.toList());
System.out.println("所有技能: " + allSkills);
运行结果:
展平后: [a, b, c, d, e, f]
字符列表: [H, e, l, l, o, W, o, r, l, d]
所有技能: [Java, Python, Go, Rust, C++, JavaScript]
3.3 排序
sorted() - 自然排序
方法签名:
java
Stream<T> sorted()
功能说明: 按自然顺序对流中的元素进行排序
参数解释: 无参数
返回值: 返回一个新的Stream,元素已按自然顺序排序
代码示例:
java
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("升序排序: " + sortedNumbers);
List<String> words = Arrays.asList("Java", "Python", "Go", "C");
List<String> sortedWords = words.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("字母排序: " + sortedWords);
运行结果:
升序排序: [1, 2, 3, 5, 8, 9]
字母排序: [C, Go, Java, Python]
sorted(Comparator) - 自定义排序
方法签名:
java
Stream<T> sorted(Comparator<? super T> comparator)
功能说明: 根据给定的比较器对流中的元素进行排序
参数解释:
comparator:比较器,用于定义排序规则
返回值: 返回一个新的Stream,元素已按指定规则排序
代码示例:
java
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
// 按薪资升序排序
List<Employee> bySalaryAsc = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary))
.collect(Collectors.toList());
System.out.println("按薪资升序: " + bySalaryAsc);
// 按薪资降序排序
List<Employee> bySalaryDesc = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary).reversed())
.collect(Collectors.toList());
System.out.println("按薪资降序: " + bySalaryDesc);
// 多条件排序:先按薪资,再按姓名
List<Employee> multiSort = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary)
.thenComparing(Employee::getName))
.collect(Collectors.toList());
System.out.println("多条件排序: " + multiSort);
运行结果:
按薪资升序: [张三(5000), 王五(6000), 李四(8000)]
按薪资降序: [李四(8000), 王五(6000), 张三(5000)]
多条件排序: [张三(5000), 王五(6000), 李四(8000)]
3.4 其他中间操作
peek() - 查看元素
方法签名:
java
Stream<T> peek(Consumer<? super T> action)
功能说明: 对流中的每个元素执行指定操作,主要用于调试
参数解释:
action:要对每个元素执行的操作
返回值: 返回一个新的Stream,元素不变,但每个元素都会执行action
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用peek进行调试
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("处理前: " + n))
.map(n -> n * n)
.peek(n -> System.out.println("处理后: " + n))
.collect(Collectors.toList());
System.out.println("最终结果: " + result);
运行结果:
处理前: 1
处理后: 1
处理前: 2
处理后: 4
处理前: 3
处理后: 9
处理前: 4
处理后: 16
处理前: 5
处理后: 25
最终结果: [1, 4, 9, 16, 25]
四、终端操作方法
终端操作会触发流的执行,并返回结果或产生副作用。
4.1 查找与匹配
allMatch() - 全部匹配
方法签名:
java
boolean allMatch(Predicate<? super T> predicate)
功能说明: 检查流中的所有元素是否都满足指定条件
参数解释:
predicate:断言函数,用于测试每个元素
返回值: 如果所有元素都满足条件,返回true;否则返回false
代码示例:
java
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean allEven = numbers.stream()
.allMatch(n -> n % 2 == 0);
System.out.println("全部是偶数: " + allEven);
List<Integer> numbers2 = Arrays.asList(2, 4, 6, 8, 9);
boolean allEven2 = numbers2.stream()
.allMatch(n -> n % 2 == 0);
System.out.println("全部是偶数: " + allEven2);
运行结果:
全部是偶数: true
全部是偶数: false
anyMatch() - 任意匹配
方法签名:
java
boolean anyMatch(Predicate<? super T> predicate)
功能说明: 检查流中是否有任意元素满足指定条件
参数解释:
predicate:断言函数,用于测试每个元素
返回值: 如果至少有一个元素满足条件,返回true;否则返回false
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 8);
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0);
System.out.println("存在偶数: " + hasEven);
List<String> words = Arrays.asList("Java", "Python", "Go");
boolean hasJava = words.stream()
.anyMatch(word -> word.equals("Java"));
System.out.println("包含Java: " + hasJava);
运行结果:
存在偶数: true
包含Java: true
noneMatch() - 无匹配
方法签名:
java
boolean noneMatch(Predicate<? super T> predicate)
功能说明: 检查流中是否没有元素满足指定条件
参数解释:
predicate:断言函数,用于测试每个元素
返回值: 如果没有元素满足条件,返回true;否则返回false
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9);
boolean noEven = numbers.stream()
.noneMatch(n -> n % 2 == 0);
System.out.println("没有偶数: " + noEven);
List<String> words = Arrays.asList("Java", "Python", "Go");
boolean noC = words.stream()
.noneMatch(word -> word.startsWith("C"));
System.out.println("没有C开头的单词: " + noC);
运行结果:
没有偶数: true
没有C开头的单词: true
findFirst() - 查找第一个
方法签名:
java
Optional<T> findFirst()
功能说明: 返回流中的第一个元素
参数解释: 无参数
返回值: 返回一个Optional,包含第一个元素;如果流为空,返回空的Optional
代码示例:
java
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
Optional<Integer> first = numbers.stream()
.findFirst();
System.out.println("第一个元素: " + first.orElse(-1));
List<String> words = Arrays.asList("Java", "Python", "Go");
Optional<String> firstLong = words.stream()
.filter(word -> word.length() > 3)
.findFirst();
System.out.println("第一个长单词: " + firstLong.orElse("无"));
运行结果:
第一个元素: 3
第一个长单词: Java
findAny() - 查找任意一个
方法签名:
java
Optional<T> findAny()
功能说明: 返回流中的任意一个元素
参数解释: 无参数
返回值: 返回一个Optional,包含任意一个元素;如果流为空,返回空的Optional
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> any = numbers.stream()
.findAny();
System.out.println("任意元素: " + any.orElse(-1));
// 在并行流中,findAny可能返回不同的元素
List<Integer> parallelResult = numbers.parallelStream()
.filter(n -> n > 2)
.findAny();
System.out.println("并行流中大于2的任意元素: " + parallelResult.orElse(-1));
运行结果:
任意元素: 1
并行流中大于2的任意元素: 3(或4、5)
4.2 归约
reduce() - 归约操作
方法签名:
java
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
功能说明: 将流中的元素组合成一个结果
参数解释:
identity:初始值accumulator:累加器函数,定义如何组合元素combiner:合并器函数,用于并行流时合并部分结果
返回值: 返回归约后的结果
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 无初始值,返回Optional
Optional<Integer> sum1 = numbers.stream()
.reduce((a, b) -> a + b);
System.out.println("求和(无初始值): " + sum1.orElse(0));
// 有初始值
int sum2 = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("求和(有初始值): " + sum2);
// 使用方法引用
int sum3 = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("求和(方法引用): " + sum3);
// 求最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
System.out.println("最大值: " + max.orElse(0));
// 求最小值
Optional<Integer> min = numbers.stream()
.reduce(Integer::min);
System.out.println("最小值: " + min.orElse(0));
// 求乘积
int product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println("乘积: " + product);
// 字符串连接
List<String> words = Arrays.asList("Java", "Stream", "API");
String joined = words.stream()
.reduce("", (a, b) -> a + b);
System.out.println("字符串连接: " + joined);
String joinedWithComma = words.stream()
.reduce((a, b) -> a + "," + b)
.orElse("");
System.out.println("用逗号连接: " + joinedWithComma);
运行结果:
求和(无初始值): 15
求和(有初始值): 15
求和(方法引用): 15
最大值: 5
最小值: 1
乘积: 120
字符串连接: JavaStreamAPI
用逗号连接: Java,Stream,API
4.3 收集
collect() - 收集结果
方法签名:
java
<R> R collect(Collector<? super T, A, R> collector)
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
功能说明: 将流中的元素收集到容器中
参数解释:
collector:收集器,定义如何收集元素supplier:提供结果容器accumulator:累加器,将元素添加到容器combiner:合并器,用于并行流时合并部分结果
返回值: 返回收集后的结果
Collectors常用方法
toList() - 收集到List
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("偶数列表: " + evenNumbers);
运行结果:
偶数列表: [2, 4]
toSet() - 收集到Set
java
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
Set<Integer> uniqueNumbers = numbers.stream()
.collect(Collectors.toSet());
System.out.println("唯一数字: " + uniqueNumbers);
运行结果:
唯一数字: [1, 2, 3, 4]
toCollection() - 收集到指定集合
java
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
LinkedList<Integer> linkedList = numbers.stream()
.sorted()
.collect(Collectors.toCollection(LinkedList::new));
System.out.println("有序链表: " + linkedList);
运行结果:
有序链表: [1, 2, 5, 8, 9]
toMap() - 收集到Map
java
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
// 转换为Map:姓名->员工对象
Map<String, Employee> nameToEmployee = employees.stream()
.collect(Collectors.toMap(Employee::getName, e -> e));
System.out.println("姓名到员工: " + nameToEmployee);
// 转换为Map:姓名->薪资
Map<String, Integer> nameToSalary = employees.stream()
.collect(Collectors.toMap(Employee::getName, Employee::getSalary));
System.out.println("姓名到薪资: " + nameToSalary);
// 处理键冲突(使用新值)
List<Employee> employeesWithConflict = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("张三", 7000) // 同名员工
);
Map<String, Integer> handleConflict = employeesWithConflict.stream()
.collect(Collectors.toMap(
Employee::getName,
Employee::getSalary,
(oldValue, newValue) -> newValue
));
System.out.println("处理键冲突: " + handleConflict);
运行结果:
姓名到员工: {张三=张三(5000), 李四=李四(8000), 王五=王五(6000)}
姓名到薪资: {张三=5000, 李四=8000, 王五=6000}
处理键冲突: {张三=7000, 李四=8000}
joining() - 字符串连接
java
List<String> words = Arrays.asList("Java", "Stream", "API");
// 直接连接
String joined1 = words.stream()
.collect(Collectors.joining());
System.out.println("直接连接: " + joined1);
// 用逗号分隔
String joined2 = words.stream()
.collect(Collectors.joining(", "));
System.out.println("逗号分隔: " + joined2);
// 用分隔符、前缀、后缀
String joined3 = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println("带前后缀: " + joined3);
运行结果:
直接连接: JavaStreamAPI
逗号分隔: Java, Stream, API
带前后缀: [Java, Stream, API]
groupingBy() - 分组
java
class Employee {
String name;
String department;
int salary;
Employee(String name, String department, int salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("张三", "技术部", 5000),
new Employee("李四", "技术部", 8000),
new Employee("王五", "市场部", 6000),
new Employee("赵六", "市场部", 7000),
new Employee("孙七", "财务部", 5500)
);
// 按部门分组
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
System.out.println("按部门分组: " + byDept);
// 分组后计数
Map<String, Long> countByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting()));
System.out.println("各部门人数: " + countByDept);
// 分组后求平均值
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingInt(Employee::getSalary)
));
System.out.println("各部门平均薪资: " + avgSalaryByDept);
// 多级分组:先按部门,再按薪资范围
Map<String, Map<String, List<Employee>>> multiGroup = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.groupingBy(e -> e.getSalary() > 6000 ? "高薪" : "普通")
));
System.out.println("多级分组: " + multiGroup);
运行结果:
按部门分组: {技术部=[张三(5000), 李四(8000)], 市场部=[王五(6000), 赵六(7000)], 财务部=[孙七(5500)]}
各部门人数: {技术部=2, 市场部=2, 财务部=1}
各部门平均薪资: {技术部=6500.0, 市场部=6500.0, 财务部=5500.0}
多级分组: {技术部={普通=[张三(5000)], 高薪=[李四(8000)]}, 市场部={普通=[王五(6000)], 高薪=[赵六(7000)]}, 财务部={普通=[孙七(5500)]}}
partitioningBy() - 分区
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 按是否为偶数分区
Map<Boolean, List<Integer>> partitionByEven = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println("按奇偶分区: " + partitionByEven);
// 分区后计数
Map<Boolean, Long> countPartition = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting()));
System.out.println("奇偶数量: " + countPartition);
// 按薪资是否大于6000分区
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000),
new Employee("赵六", 7000)
);
Map<Boolean, List<Employee>> partitionBySalary = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 6000));
System.out.println("按薪资分区: " + partitionBySalary);
运行结果:
按奇偶分区: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
奇偶数量: {false=5, true=5}
按薪资分区: {false=[张三(5000), 王五(6000)], true=[李四(8000), 赵六(7000)]}
mapping() - 映射后收集
java
List<String> words = Arrays.asList("java", "stream", "api", "lambda");
// 转换为大写后收集
List<String> upperWords = words.stream()
.collect(Collectors.mapping(String::toUpperCase, Collectors.toList()));
System.out.println("大写单词: " + upperWords);
// 分组后映射
Map<Integer, List<String>> byLength = words.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.mapping(String::toUpperCase, Collectors.toList())
));
System.out.println("按长度分组并转大写: " + byLength);
运行结果:
大写单词: [JAVA, STREAM, API, LAMBDA]
按长度分组并转大写: {4=[JAVA, API], 6=[STREAM, LAMBDA]}
4.4 统计
count() - 计数
方法签名:
java
long count()
功能说明: 返回流中元素的数量
参数解释: 无参数
返回值: 返回元素个数
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 总数
long total = numbers.stream().count();
System.out.println("总数: " + total);
// 偶数个数
long evenCount = numbers.stream()
.filter(n -> n % 2 == 0)
.count();
System.out.println("偶数个数: " + evenCount);
List<String> words = Arrays.asList("Java", "Python", "Go", "JavaScript");
long longWordCount = words.stream()
.filter(word -> word.length() > 3)
.count();
System.out.println("长单词个数: " + longWordCount);
运行结果:
总数: 10
偶数个数: 5
长单词个数: 3
max() - 最大值
方法签名:
java
Optional<T> max(Comparator<? super T> comparator)
功能说明: 根据比较器返回流中的最大元素
参数解释:
comparator:比较器,用于比较元素大小
返回值: 返回Optional,包含最大元素;如果流为空,返回空的Optional
代码示例:
java
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
Optional<Integer> maxNumber = numbers.stream()
.max(Comparator.naturalOrder());
System.out.println("最大数字: " + maxNumber.orElse(-1));
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
Optional<Employee> highestPaid = employees.stream()
.max(Comparator.comparingInt(Employee::getSalary));
System.out.println("最高薪资员工: " + highestPaid);
运行结果:
最大数字: 9
最高薪资员工: 李四(8000)
min() - 最小值
方法签名:
java
Optional<T> min(Comparator<? super T> comparator)
功能说明: 根据比较器返回流中的最小元素
参数解释:
comparator:比较器,用于比较元素大小
返回值: 返回Optional,包含最小元素;如果流为空,返回空的Optional
代码示例:
java
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
Optional<Integer> minNumber = numbers.stream()
.min(Comparator.naturalOrder());
System.out.println("最小数字: " + minNumber.orElse(-1));
List<String> words = Arrays.asList("Java", "Python", "Go", "C");
Optional<String> shortest = words.stream()
.min(Comparator.comparingInt(String::length));
System.out.println("最短单词: " + shortest.orElse("无"));
运行结果:
最小数字: 1
最短单词: C
average() - 平均值(IntStream/LongStream/DoubleStream)
方法签名:
java
OptionalDouble average()
功能说明: 计算流中元素的平均值
参数解释: 无参数
返回值: 返回OptionalDouble,包含平均值;如果流为空,返回空的OptionalDouble
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算平均值
OptionalDouble average = numbers.stream()
.mapToInt(Integer::intValue)
.average();
System.out.println("平均值: " + average.orElse(0));
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
OptionalDouble avgSalary = employees.stream()
.mapToInt(Employee::getSalary)
.average();
System.out.println("平均薪资: " + avgSalary.orElse(0));
// 使用Collectors.averagingInt
Double avgSalary2 = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
System.out.println("平均薪资(方式2): " + avgSalary2);
运行结果:
平均值: 3.0
平均薪资: 6333.333333333333
平均薪资(方式2): 6333.333333333333
sum() - 求和(IntStream/LongStream/DoubleStream)
方法签名:
java
int sum() // IntStream
long sum() // LongStream
double sum() // DoubleStream
功能说明: 计算流中元素的总和
参数解释: 无参数
返回值: 返回元素的和
代码示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("求和: " + sum);
// 使用reduce求和
int sum2 = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("求和(reduce): " + sum2);
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
int totalSalary = employees.stream()
.mapToInt(Employee::getSalary)
.sum();
System.out.println("总薪资: " + totalSalary);
运行结果:
求和: 15
求和(reduce): 15
总薪资: 19000
summarizingInt/Long/Double - 综合统计
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("综合统计:");
System.out.println(" 个数: " + stats.getCount());
System.out.println(" 总和: " + stats.getSum());
System.out.println(" 平均值: " + stats.getAverage());
System.out.println(" 最小值: " + stats.getMin());
System.out.println(" 最大值: " + stats.getMax());
List<Employee> employees = Arrays.asList(
new Employee("张三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
IntSummaryStatistics salaryStats = employees.stream()
.collect(Collectors.summarizingInt(Employee::getSalary));
System.out.println("\n薪资统计:");
System.out.println(" 人数: " + salaryStats.getCount());
System.out.println(" 总薪资: " + salaryStats.getSum());
System.out.println(" 平均薪资: " + salaryStats.getAverage());
System.out.println(" 最低薪资: " + salaryStats.getMin());
System.out.println(" 最高薪资: " + salaryStats.getMax());
运行结果:
综合统计:
个数: 10
总和: 55
平均值: 5.5
最小值: 1
最大值: 10
薪资统计:
人数: 3
总薪资: 19000
平均薪资: 6333.333333333333
最低薪资: 5000
最高薪资: 8000
五、实际应用案例
案例一:电商订单数据处理
场景描述: 处理订单数据,统计各商品类别的销售情况。
java
import java.util.*;
import java.util.stream.Collectors;
class OrderItem {
String productId;
String productName;
String category;
double price;
int quantity;
OrderItem(String productId, String productName, String category, double price, int quantity) {
this.productId = productId;
this.productName = productName;
this.category = category;
this.price = price;
this.quantity = quantity;
}
public String getCategory() { return category; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public double getTotal() { return price * quantity; }
@Override
public String toString() {
return productName + "(" + category + ") - 数量:" + quantity + " 单价:" + price;
}
}
class Order {
String orderId;
List<OrderItem> items;
Date orderDate;
Order(String orderId, List<OrderItem> items, Date orderDate) {
this.orderId = orderId;
this.items = items;
this.orderDate = orderDate;
}
public List<OrderItem> getItems() { return items; }
public Date getOrderDate() { return orderDate; }
}
public class EcommerceAnalysis {
public static void main(String[] args) {
// 创建订单数据
List<Order> orders = Arrays.asList(
new Order("ORD001", Arrays.asList(
new OrderItem("P001", "手机", "电子产品", 3999.0, 1),
new OrderItem("P002", "耳机", "电子产品", 299.0, 2)
), new Date()),
new Order("ORD002", Arrays.asList(
new OrderItem("P003", "T恤", "服装", 199.0, 3),
new OrderItem("P004", "牛仔裤", "服装", 299.0, 2)
), new Date()),
new Order("ORD003", Arrays.asList(
new OrderItem("P001", "手机", "电子产品", 3999.0, 2),
new OrderItem("P005", "运动鞋", "鞋类", 599.0, 1)
), new Date())
);
// 1. 统计各类别的销售额
Map<String, Double> salesByCategory = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getCategory,
Collectors.summingDouble(OrderItem::getTotal)
));
System.out.println("各类别销售额: " + salesByCategory);
// 2. 找出销量最高的商品
Optional<OrderItem> bestSellingItem = orders.stream()
.flatMap(order -> order.getItems().stream())
.max(Comparator.comparingInt(OrderItem::getQuantity));
System.out.println("\n销量最高商品: " + bestSellingItem);
// 3. 计算订单总金额
double totalRevenue = orders.stream()
.flatMap(order -> order.getItems().stream())
.mapToDouble(OrderItem::getTotal)
.sum();
System.out.println("\n总销售额: " + totalRevenue);
// 4. 按类别分组,统计每个类别的商品数量和平均价格
Map<String, Map<String, Object>> categoryStats = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getCategory,
Collectors.collectingAndThen(
Collectors.toList(),
items -> {
Map<String, Object> stats = new HashMap<>();
stats.put("商品数量", items.size());
stats.put("平均单价", items.stream().collect(Collectors.averagingDouble(OrderItem::getPrice)));
stats.put("总销量", items.stream().mapToInt(OrderItem::getQuantity).sum());
return stats;
}
)
));
System.out.println("\n类别统计: " + categoryStats);
// 5. 筛选出电子产品订单,并按价格排序
List<OrderItem> electronicsSorted = orders.stream()
.flatMap(order -> order.getItems().stream())
.filter(item -> item.getCategory().equals("电子产品"))
.sorted(Comparator.comparingDouble(OrderItem::getPrice).reversed())
.collect(Collectors.toList());
System.out.println("\n电子产品(按价格降序): " + electronicsSorted);
}
}
运行结果:
各类别销售额: {电子产品=9296.0, 服装=1194.0, 鞋类=599.0}
销量最高商品: 手机(电子产品) - 数量:2 单价:3999.0
总销售额: 11089.0
类别统计: {
电子产品={商品数量=3, 平均单价=2765.6666666666665, 总销量=5},
服装={商品数量=2, 平均单价=249.0, 总销量=5},
鞋类={商品数量=1, 平均单价=599.0, 总销量=1}
}
电子产品(按价格降序): [手机(电子产品) - 数量:1 单价:3999.0, 手机(电子产品) - 数量:2 单价:3999.0, 耳机(电子产品) - 数量:2 单价:299.0]
案例二:学生成绩管理系统
场景描述: 处理学生成绩数据,进行成绩统计和分析。
java
import java.util.*;
import java.util.stream.Collectors;
class Student {
String id;
String name;
String className;
Map<String, Integer> scores; // 科目 -> 分数
Student(String id, String name, String className, Map<String, Integer> scores) {
this.id = id;
this.name = name;
this.className = className;
this.scores = scores;
}
public String getId() { return id; }
public String getName() { return name; }
public String getClassName() { return className; }
public Map<String, Integer> getScores() { return scores; }
public double getAverageScore() {
return scores.values().stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0);
}
public int getTotalScore() {
return scores.values().stream()
.mapToInt(Integer::intValue)
.sum();
}
@Override
public String toString() {
return name + "(" + className + ") - 平均分:" + String.format("%.1f", getAverageScore());
}
}
public class StudentGradeAnalysis {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("S001", "张三", "一班",
Map.of("语文", 85, "数学", 90, "英语", 88, "物理", 92)),
new Student("S002", "李四", "一班",
Map.of("语文", 78, "数学", 85, "英语", 80, "物理", 75)),
new Student("S003", "王五", "二班",
Map.of("语文", 92, "数学", 88, "英语", 95, "物理", 90)),
new Student("S004", "赵六", "二班",
Map.of("语文", 65, "数学", 70, "英语", 68, "物理", 72)),
new Student("S005", "孙七", "一班",
Map.of("语文", 88, "数学", 95, "英语", 90, "物理", 93))
);
// 1. 计算全班各科平均分
Map<String, Double> subjectAvg = students.stream()
.flatMap(student -> student.getScores().entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.averagingInt(Map.Entry::getValue)
));
System.out.println("各科平均分: " + subjectAvg);
// 2. 找出总分最高的学生
Optional<Student> topStudent = students.stream()
.max(Comparator.comparingInt(Student::getTotalScore));
System.out.println("\n总分最高学生: " + topStudent);
// 3. 按班级分组,计算每个班级的平均分
Map<String, Double> classAvg = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.averagingDouble(Student::getAverageScore)
));
System.out.println("\n各班级平均分: " + classAvg);
// 4. 找出各科分数最高的学生
Map<String, Student> topBySubject = students.stream()
.flatMap(student -> student.getScores().entrySet().stream()
.map(entry -> Map.entry(entry.getKey(),
Map.entry(student, entry.getValue()))))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.collectingAndThen(
Collectors.maxBy(
Comparator.comparingInt(e -> e.getValue().getValue())
),
opt -> opt.map(Map.Entry::getValue).map(Map.Entry::getKey).orElse(null)
)
));
System.out.println("\n各科最高分学生:");
topBySubject.forEach((subject, student) ->
System.out.println(" " + subject + ": " + student.getName() +
"(" + student.getScores().get(subject) + "分)"));
// 5. 统计分数段分布(以数学为例)
String targetSubject = "数学";
Map<String, Long> gradeDistribution = students.stream()
.filter(s -> s.getScores().containsKey(targetSubject))
.collect(Collectors.groupingBy(
student -> {
int score = student.getScores().get(targetSubject);
if (score >= 90) return "优秀";
else if (score >= 80) return "良好";
else if (score >= 70) return "中等";
else if (score >= 60) return "及格";
else return "不及格";
},
Collectors.counting()
));
System.out.println("\n" + targetSubject + "分数段分布: " + gradeDistribution);
// 6. 找出所有科目都及格的学生
List<Student> allPass = students.stream()
.filter(student -> student.getScores().values().stream()
.allMatch(score -> score >= 60))
.collect(Collectors.toList());
System.out.println("\n所有科目及格的学生: " + allPass);
// 7. 按总分排名
List<Student> ranked = students.stream()
.sorted(Comparator.comparingInt(Student::getTotalScore).reversed())
.collect(Collectors.toList());
System.out.println("\n学生总分排名:");
for (int i = 0; i < ranked.size(); i++) {
System.out.printf("第%d名: %s - 总分: %d\n",
i + 1, ranked.get(i).getName(), ranked.get(i).getTotalScore());
}
}
}
运行结果:
各科平均分: {语文=81.6, 数学=85.6, 英语=84.2, 物理=86.4}
总分最高学生: 孙七(一班) - 平均分:91.5
各班级平均分: {一班=87.16666666666667, 二班=78.5}
各科最高分学生:
语文: 王五(92分)
数学: 孙七(95分)
英语: 王五(95分)
物理: 孙七(93分)
数学分数段分布: {良好=3, 优秀=2}
所有科目及格的学生: [张三(一班) - 平均分:88.8, 李四(一班) - 平均分:79.5, 王五(二班) - 平均分:91.2, 孙七(一班) - 平均分:91.5]
学生总分排名:
第1名: 孙七 - 总分:366
第2名: 王五 - 总分:365
第3名: 张三 - 总分:355
第4名: 李四 - 总分:318
第5名: 赵六 - 总分:275
案例三:文本分析与词频统计
场景描述: 分析文本内容,统计词频、查找最长单词等。
java
import java.util.*;
import java.util.stream.Collectors;
import java.util.regex.Pattern;
public class TextAnalysis {
public static void main(String[] args) {
String text = "Java Stream API is a powerful tool for data processing. " +
"It provides a declarative approach to process collections. " +
"Stream operations can be chained together to form pipelines. " +
"The API supports both sequential and parallel processing.";
// 1. 分词并统计词频
Map<String, Long> wordCount = Pattern.compile("\\s+")
.splitAsStream(text.toLowerCase()
.replaceAll("[^a-zA-Z\\s]", ""))
.filter(word -> word.length() > 2) // 过滤掉长度<=2的词
.collect(Collectors.groupingBy(
word -> word,
Collectors.counting()
));
System.out.println("词频统计:");
wordCount.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry -> System.out.printf(" %s: %d\n", entry.getKey(), entry.getValue()));
// 2. 找出最长的单词
String longestWord = Pattern.compile("\\s+")
.splitAsStream(text.replaceAll("[^a-zA-Z\\s]", ""))
.max(Comparator.comparingInt(String::length))
.orElse("");
System.out.println("\n最长的单词: " + longestWord + " (长度: " + longestWord.length() + ")");
// 3. 统计不同长度单词的数量
Map<Integer, Long> lengthDistribution = Pattern.compile("\\s+")
.splitAsStream(text.replaceAll("[^a-zA-Z\\s]", ""))
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
System.out.println("\n单词长度分布:");
lengthDistribution.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> System.out.printf(" 长度%d: %d个\n", entry.getKey(), entry.getValue()));
// 4. 找出所有以特定字母开头的单词
char startLetter = 'p';
List<String> wordsStartingWith = Pattern.compile("\\s+")
.splitAsStream(text.toLowerCase()
.replaceAll("[^a-zA-Z\\s]", ""))
.filter(word -> word.startsWith(String.valueOf(startLetter)))
.distinct()
.collect(Collectors.toList());
System.out.println("\n以'" + startLetter + "'开头的单词: " + wordsStartingWith);
// 5. 检查文本是否包含特定词汇
List<String> targetWords = Arrays.asList("stream", "api", "data", "parallel");
Map<String, Boolean> wordPresence = targetWords.stream()
.collect(Collectors.toMap(
word -> word,
word -> text.toLowerCase().contains(word.toLowerCase())
));
System.out.println("\n关键词检查:");
wordPresence.forEach((word, present) ->
System.out.println(" " + word + ": " + (present ? "存在" : "不存在")));
// 6. 提取所有单词并按字母顺序排序
List<String> allWordsSorted = Pattern.compile("\\s+")
.splitAsStream(text.toLowerCase()
.replaceAll("[^a-zA-Z\\s]", ""))
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("\n所有单词(排序后):");
System.out.println(" " + String.join(", ", allWordsSorted));
// 7. 计算文本的基本统计信息
String[] words = text.replaceAll("[^a-zA-Z\\s]", "").split("\\s+");
String[] sentences = text.split("[.!?]");
String[] characters = text.split("");
System.out.println("\n文本统计:");
System.out.println(" 单词数: " + words.length);
System.out.println(" 句子数: " + sentences.length);
System.out.println(" 字符数: " + characters.length);
System.out.println(" 平均单词长度: " +
Arrays.stream(words).mapToInt(String::length).average().orElse(0));
}
}
运行结果:
词频统计:
stream: 3
a: 3
processing: 2
the: 2
api: 2
declarative: 1
provides: 1
approach: 1
process: 1
collections: 1
最长的单词: processing (长度: 10)
单词长度分布:
长度1: 0个
长度2: 0个
长度3: 1个
长度4: 6个
长度5: 4个
长度6: 3个
长度7: 1个
长度8: 2个
长度10: 2个
长度11: 1个
以'p'开头的单词: [processing, provides, process, pipelines, parallel]
关键词检查:
stream: 存在
api: 存在
data: 存在
parallel: 存在
所有单词(排序后):
a, and, api, approach, be, both, can, chains, collections, data, declarative, for, form, is, it, operations, pipelines, powerful, process, processing, provides, sequential, stream, supports, the, tool
文本统计:
单词数: 26
句子数: 4
字符数: 194
平均单词长度: 5.846153846153846
案例四:员工信息管理
场景描述: 管理员工信息,进行查询、分组、统计分析。
java
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
class Employee {
String id;
String name;
String department;
String position;
int salary;
LocalDate hireDate;
int age;
Employee(String id, String name, String department, String position,
int salary, LocalDate hireDate, int age) {
this.id = id;
this.name = name;
this.department = department;
this.position = position;
this.salary = salary;
this.hireDate = hireDate;
this.age = age;
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getDepartment() { return department; }
public String getPosition() { return position; }
public int getSalary() { return salary; }
public LocalDate getHireDate() { return hireDate; }
public int getAge() { return age; }
// 计算工龄(年)
public int getYearsOfService() {
return LocalDate.now().getYear() - hireDate.getYear();
}
@Override
public String toString() {
return String.format("%s(%s) - %s, 薪资:%d", name, department, position, salary);
}
}
public class EmployeeManagement {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("E001", "张三", "技术部", "高级工程师", 25000,
LocalDate.of(2018, 3, 15), 32),
new Employee("E002", "李四", "技术部", "中级工程师", 18000,
LocalDate.of(2020, 7, 1), 28),
new Employee("E003", "王五", "技术部", "初级工程师", 12000,
LocalDate.of(2022, 1, 10), 25),
new Employee("E004", "赵六", "市场部", "市场经理", 22000,
LocalDate.of(2019, 5, 20), 35),
new Employee("E005", "孙七", "市场部", "销售代表", 10000,
LocalDate.of(2021, 9, 1), 26),
new Employee("E006", "周八", "财务部", "财务经理", 20000,
LocalDate.of(2017, 11, 15), 38),
new Employee("E007", "吴九", "技术部", "架构师", 30000,
LocalDate.of(2016, 2, 1), 36),
new Employee("E008", "郑十", "市场部", "市场专员", 9000,
LocalDate.of(2023, 3, 1), 24)
);
// 1. 按部门统计员工数量和平均薪资
Map<String, Map<String, Object>> deptStats = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
empList -> {
Map<String, Object> stats = new HashMap<>();
stats.put("人数", empList.size());
stats.put("平均薪资", empList.stream()
.collect(Collectors.averagingInt(Employee::getSalary)));
stats.put("薪资总和", empList.stream()
.mapToInt(Employee::getSalary).sum());
return stats;
}
)
));
System.out.println("部门统计:");
deptStats.forEach((dept, stats) ->
System.out.printf(" %s: 人数=%d, 平均薪资=%.0f, 总和=%d\n",
dept, stats.get("人数"), stats.get("平均薪资"), stats.get("薪资总和")));
// 2. 找出薪资前3的员工
List<Employee> topSalaries = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary).reversed())
.limit(3)
.collect(Collectors.toList());
System.out.println("\n薪资前3的员工:");
topSalaries.forEach(emp -> System.out.println(" " + emp));
// 3. 找出技术部工龄超过5年的员工
List<Employee> techLongService = employees.stream()
.filter(emp -> emp.getDepartment().equals("技术部"))
.filter(emp -> emp.getYearsOfService() > 5)
.collect(Collectors.toList());
System.out.println("\n技术部工龄超过5年的员工:");
techLongService.forEach(emp -> System.out.println(" " + emp +
", 工龄:" + emp.getYearsOfService() + "年"));
// 4. 按薪资区间分组
Map<String, List<Employee>> salaryRanges = employees.stream()
.collect(Collectors.groupingBy(emp -> {
int salary = emp.getSalary();
if (salary >= 25000) return "25000+";
else if (salary >= 20000) return "20000-24999";
else if (salary >= 15000) return "15000-19999";
else return "<15000";
}));
System.out.println("\n按薪资区间分组:");
salaryRanges.forEach((range, empList) ->
System.out.println(" " + range + ": " + empList.size() + "人"));
// 5. 统计各职位的人数
Map<String, Long> positionCount = employees.stream()
.collect(Collectors.groupingBy(Employee::getPosition, Collectors.counting()));
System.out.println("\n各职位人数:");
positionCount.forEach((position, count) ->
System.out.println(" " + position + ": " + count + "人"));
// 6. 找出年龄在30-35岁之间的员工
List<Employee> ageRange = employees.stream()
.filter(emp -> emp.getAge() >= 30 && emp.getAge() <= 35)
.sorted(Comparator.comparingInt(Employee::getAge))
.collect(Collectors.toList());
System.out.println("\n年龄在30-35岁的员工:");
ageRange.forEach(emp -> System.out.println(" " + emp + ", 年龄:" + emp.getAge()));
// 7. 计算整体薪资分布
IntSummaryStatistics salaryStats = employees.stream()
.mapToInt(Employee::getSalary)
.summaryStatistics();
System.out.println("\n整体薪资统计:");
System.out.println(" 人数: " + salaryStats.getCount());
System.out.println(" 总和: " + salaryStats.getSum());
System.out.println(" 平均: " + (int)salaryStats.getAverage());
System.out.println(" 最高: " + salaryStats.getMax());
System.out.println(" 最低: " + salaryStats.getMin());
// 8. 查找每个部门薪资最高的员工
Map<String, Employee> highestPaidByDept = employees.stream()
.collect(Collectors.toMap(
Employee::getDepartment,
emp -> emp,
(e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2
));
System.out.println("\n各部门薪资最高的员工:");
highestPaidByDept.forEach((dept, emp) ->
System.out.println(" " + dept + ": " + emp.getName() + " (" + emp.getSalary() + ")"));
}
}
运行结果:
部门统计:
技术部: 人数=4, 平均薪资=21250, 总和=85000
市场部: 人数=3, 平均薪资=13666, 总和=41000
财务部: 人数=1, 平均薪资=20000, 总和=20000
薪资前3的员工:
吴九(技术部) - 架构师, 薪资:30000
张三(技术部) - 高级工程师, 薪资:25000
赵六(市场部) - 市场经理, 薪资:22000
技术部工龄超过5年的员工:
吴九(技术部) - 架构师, 薪资:30000, 工龄:10年
张三(技术部) - 高级工程师, 薪资:25000, 工龄:8年
按薪资区间分组:
25000+: 2人
20000-24999: 2人
15000-19999: 1人
<15000: 3人
各职位人数:
高级工程师: 1人
中级工程师: 1人
初级工程师: 1人
市场经理: 1人
销售代表: 1人
财务经理: 1人
架构师: 1人
市场专员: 1人
年龄在30-35岁的员工:
李四(技术部) - 中级工程师, 薪资:18000, 年龄:28
赵六(市场部) - 市场经理, 薪资:22000, 年龄:35
整体薪资统计:
人数: 8
总和: 146000
平均: 18250
最高: 30000
最低: 9000
各部门薪资最高的员工:
技术部: 吴九 (30000)
市场部: 赵六 (22000)
财务部: 周八 (20000)
六、注意事项与最佳实践
6.1 Stream的特点与限制
Stream只能消费一次
java
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
stream.count(); // 正确执行
stream.count(); // 抛出IllegalStateException
解决方案: 每次需要使用Stream时重新创建
java
List<String> list = Arrays.asList("a", "b", "c");
list.stream().count(); // 第一次使用
list.stream().count(); // 第二次使用,重新创建Stream
Stream操作是惰性的
java
List<String> list = Arrays.asList("a", "bb", "ccc", "dddd");
Stream<String> stream = list.stream()
.filter(s -> {
System.out.println("filter: " + s);
return s.length() > 2;
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
}); // 此时不会执行任何操作
stream.collect(Collectors.toList()); // 触发执行
执行结果:
filter: a
filter: bb
filter: ccc
map: ccc
filter: dddd
map: dddd
6.2 性能考量
适当使用基本类型Stream
java
// 避免装箱操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 不推荐:使用Stream<Integer>
int sum1 = numbers.stream()
.reduce(0, Integer::sum); // 会发生装箱拆箱
// 推荐:使用IntStream
int sum2 = numbers.stream()
.mapToInt(Integer::intValue)
.sum(); // 避免装箱拆箱,性能更好
合理使用并行流
java
List<Integer> numbers = IntStream.range(1, 1000000).boxed().collect(Collectors.toList());
// 顺序流
long start1 = System.currentTimeMillis();
long sum1 = numbers.stream().reduce(0L, Long::sum);
long time1 = System.currentTimeMillis() - start1;
// 并行流
long start2 = System.currentTimeMillis();
long sum2 = numbers.parallelStream().reduce(0L, Long::sum);
long time2 = System.currentTimeMillis() - start2;
System.out.println("顺序流: " + time1 + "ms");
System.out.println("并行流: " + time2 + "ms");
使用并行流的注意事项:
- 数据量较大时(通常>10000元素)才考虑使用并行流
- 数据源易于拆分(如ArrayList)时效果好
- 避免在并行流中使用共享状态
- 操作本身耗时较长时,并行流优势更明显
避免过度使用Stream
java
// 简单操作,传统方式可能更清晰
List<String> names = Arrays.asList("张三", "李四", "王五");
// Stream方式
List<String> result = names.stream()
.filter(name -> name.startsWith("张"))
.collect(Collectors.toList());
// 传统方式(更直接)
List<String> result2 = new ArrayList<>();
for (String name : names) {
if (name.startsWith("张")) {
result2.add(name);
}
}
6.3 常见陷阱
避免在Stream中修改外部状态
java
// 不推荐:修改外部变量
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int count = 0;
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> count++); // 并行流时可能出错
// 推荐:使用count()
long evenCount = numbers.stream()
.filter(n -> n % 2 == 0)
.count();
注意Optional的使用
java
List<String> names = Arrays.asList("张三", "李四", "王五");
// 安全地处理Optional
Optional<String> result = names.stream()
.filter(name -> name.startsWith("赵"))
.findFirst();
// 不推荐:直接get()
// String s = result.get(); // 可能抛出NoSuchElementException
// 推荐:使用orElse()或其他安全方法
String s = result.orElse("未找到");
String s2 = result.orElseGet(() -> "未找到");
result.ifPresent(name -> System.out.println("找到: " + name));
注意forEach的顺序
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 顺序流:保证顺序
numbers.stream().forEach(n -> System.out.print(n + " ")); // 1 2 3 4 5
// 并行流:不保证顺序
numbers.parallelStream().forEach(n -> System.out.print(n + " ")); // 顺序不确定
// 并行流需要顺序时,使用forEachOrdered
numbers.parallelStream().forEachOrdered(n -> System.out.print(n + " ")); // 1 2 3 4 5
避免在filter中使用副作用
java
// 不推荐:filter中有副作用
List<String> names = new ArrayList<>();
List<String> input = Arrays.asList("a", "b", "c");
input.stream()
.filter(s -> {
names.add(s); // 副作用,不可预测
return s.length() > 1;
})
.collect(Collectors.toList());
// 推荐:分离操作
List<String> names2 = input.stream()
.filter(s -> s.length() > 1)
.collect(Collectors.toList());
6.4 最佳实践总结
| 实践 | 说明 |
|---|---|
| 使用方法引用 | String::length 比 s -> s.length() 更简洁 |
| 链式调用 | 将多个操作串联在一起,代码更清晰 |
| 合理使用peek | 只在调试时使用,不要用于业务逻辑 |
| 善用Collectors | 熟练使用Collectors工具类的各种方法 |
| 注意空值处理 | 使用Optional处理可能为空的结果 |
| 考虑性能 | 大数据集考虑并行流,小数据集使用顺序流 |
| 保持简洁 | 过长的流水线考虑拆分 |
| 测试验证 | 确保Stream操作的正确性 |
七、总结
7.1 Stream API的核心价值
Java Stream API 是现代Java开发中不可或缺的工具,它带来的核心价值包括:
- 代码简洁性:声明式的编程风格让代码更加简洁、易读
- 可读性提升:链式调用清晰地表达了数据处理的意图
- 并行处理能力:轻松实现并行计算,充分利用多核CPU
- 功能强大:提供了丰富的操作方法,满足各种数据处理需求
- 函数式编程:推动了Java向函数式编程范式的演进
7.2 常用方法速查表
| 类别 | 方法 | 说明 |
|---|---|---|
| 创建 | stream() / parallelStream() | 从Collection创建 |
| Arrays.stream() | 从数组创建 | |
| Stream.of() | 从值创建 | |
| 筛选 | filter() | 过滤元素 |
| distinct() | 去重 | |
| limit() | 限制数量 | |
| skip() | 跳过元素 | |
| 映射 | map() | 元素转换 |
| flatMap() | 扁平化映射 | |
| 排序 | sorted() | 排序 |
| 匹配 | allMatch() / anyMatch() / noneMatch() | 匹配检查 |
| findFirst() / findAny() | 查找元素 | |
| 归约 | reduce() | 归约操作 |
| 收集 | collect() | 收集结果 |
| 统计 | count() / max() / min() | 统计操作 |
7.3 结语
Java Stream API 彻底改变了我们处理集合数据的方式。掌握Stream不仅能让你的代码更加优雅,还能提高开发效率。但要注意,Stream不是万能的,在简单场景下传统方式可能更直接。选择合适的工具,才能写出更好的代码。