外观
【Stream】流式处理
约 2587 字大约 9 分钟
2025-10-16
Stream是什么
Stream可以简单理解为一种高级的迭代器,一条可以对数据源进行函数式操作的流水线。但和普通迭代器的是有区别的,首先,它不是数据结构,其次,也不存储数据,只是在原有的数据集合上进行一系列的计算操作后,生成一个新的结果。
场景示例
假设有一个存储员工信息的集合,想要从里面筛选出员工工资>10000的员工并获取他们的姓名。假设使用for循环和条件判断实现,代码如下:
List<Employee> employees = getEmployees();
List<String> names = new ArrayList<>();
for (Employee employee : employees) {
if (employee.getSalary() > 10000) {
names.add(employee.getName());
}
}使用Stream实现,代码如下:
List<Employee> employees = getEmployees();
List<String> names = employees.stream()
.filter(employee -> employee.getSalary() > 10000)
.map(Employee::getName)
.collect(Collectors.toList());两种实现,Stream更加简洁,编码的人可以更加专注于 “做什么”,而不是 “怎么做”。
Stream api 实战
Stream的操作主要分为中间操作和终止操作,围绕两种类型展开。
中间操作
中间操作,顾名思义,就是在操作之后,流并没有结束和关闭,可以继续记性流式操作流,下面以实际代码示
filter 操作
filter用于筛选Stream中的元素,接收一个返回boolean值的Predicate(断言)参数,用于判断元素是否满足条件进行过滤。
例如,从一个整数列表中筛选出偶数:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出: [2, 4, 6]map 映射
map可以接收一个Function(函数)作为参数,将Stream中的元素按照指定的函数进行转换,生成一个新的元素并返回一个都是新元素的Stream。 例如,将一个字符串列表中的每个字符串转换为其长度:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); // 输出: [5, 3, 7, 5]flatMap 流扁平化
flatMap方法和map方法有点类似,主要区别在于,map操作后返回的Stream中每个元素是一个独立的对象,而flatMap操作后返回的元素是所有原始Stream中每个元素经过映射后展开的元素,通过文字,比较难以理解,可以参考下面的这个代码示例,以及后面的高级用法。 例如,有一个包含多个字符串列表的列表,将其打平成一个包含所有字符串的列表:
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("Java", "Python"),
Arrays.asList("C++", "Go"),
Arrays.asList("JavaScript", "TypeScript")
);
List<String> languages = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(languages); // 输出: [Java, Python, C++, Go, JavaScript, TypeScript]sorted 排序
sorted排序,有两种使用方式,自然排序和自定义比较器排序。自然排序要求Stream中的元素实现Comparable接口,sorted方法会按照元素的自然顺序进行排序。
例如,对一个整数列表进行自然排序:
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 9, 2);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出: [1, 2, 3, 5, 8, 9]上面代码中,Integer类实现了Comparable接口,sorted方法会根据Integer的自然顺序(从小到大)对列表中的元素进行排序。
自定义排序,则需要通过向sorted方法传递一个Comparator(比较器)对象来实现。
例如,对一个字符串列表按照长度进行排序:
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
List<String> sortedWords = words.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
System.out.println(sortedWords); // 输出: [date, apple, cherry, banana]distinct 去重
distinct是对Stream中的重复元素去重,是否重复,通过equals方法来判断元素是否相等。
例如,对一个包含重复元素的整数列表进行去重:
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]终止操作
终止操作会触发Stream返回结果,简单说,就是这个流式已经结束,无法继续操作。
collect 收集
collect是日常开发中,用得比较多的终止操作,可以将Stream中的元素收集到一个结果容器中,如列表、集合、映射等。 collect方法可以接收一个Collector(收集器)作为参数,详细可以参考jdk工具类Collectors。
常用的收集器Collectors.toList(),将元素收集到一个List中。 Collectors.toSet(),将元素收集到一个Set中,并自动去除重复元素。 Collectors.toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper),将元素收集到一个Map中,keyMapper用于指定键的映射函数,valueMapper用于指定值的映射函数。
例如,将一个整数列表中的偶数收集到一个新的列表中:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出: [2, 4, 6]再比如,根据员工的部门对员工进行分组
class Employee {
private String name;
private String department;
public Employee(String name, String department) {
this.name = name;
this.department = department;
}
public String getName() {
return name;
}
public String getDepartment() {
return department;
}
}
List<Employee> employees = Arrays.asList(
new Employee("Alice", "HR"),
new Employee("Bob", "Engineering"),
new Employee("Charlie", "HR"),
new Employee("David", "Marketing")
);
Map<String, List<Employee>> groupedEmployees = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
System.out.println(groupedEmployees);forEach 遍历
forEach接收一个一个无返回值的Consumer(消费者)作为参数,在遍历的过程中对元素进行处理。
例如,打印一个整数列表中的每个元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.forEach(System.out::println);reduce 归纳
reduce方法用于对Stream中的元素进行聚合操作,并按照指定的规则进行合并,返回结果。
例如,计算一个整数列表中所有元素的和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (acc, num) -> acc + num);
System.out.println(sum); // 输出: 15count 计数
count是统计Stream中的元素个数,并返回一个long类型的结果。
例如,统计一个整数列表中的元素个数:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream()
.count();
System.out.println(count); // 输出: 5Stream 特性
惰性求值
惰性求值就是在Stream进行,比如,filter、map、sorted等中间操作时,并不会立即执行,而是等到终止操作,如forEach、collect、reduce时,才会被调用返回结果。这种延迟执行,在处理大数据集时具有明显优势。避免不必要的计算。
例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream()
.filter(n -> {
System.out.println("filtering: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("mapping: " + n);
return n * 2;
});上面代码中,虽然定义了filter和map操作,但这些操作并不会立即执行,因为还没有调用终止操作。
List<Integer> result = stream.collect(Collectors.toList());链式调用
Stream允许我们将多个操作串联在一起,像一条流水线一样调用。
例如:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());上面代码,先用filter对流进行元素过滤,再用map对集合中的元素进行处理,最后进行排序并返回结果。
并行处理
在java8之前,如果需要充分利用cpu多核能力,只能构建多个线程。但是,Stream也提供了相应并行处理能力,可以通过调用parallel方法,可以将一个顺序流转换为并行流,多个线程并行处理。 但是这个功能不太好控制,并行处理效率不一定比单线程处理高,因为线程切换也是成本,再使用的时候,一定要做好性能测试。
例如,计算1到1000000的整数之和,使用顺序流和并行流的代码分别如下:
// 顺序流
long sequentialSum = LongStream.rangeClosed(1, 1000000)
.sum();
// 并行流
long parallelSum = LongStream.rangeClosed(1, 1000000)
.parallel()
.sum();Stream 高级使用案例
多级分组与统计
示例,有一个存储员工信息的列表,每个员工对象包含姓名、部门和职位属性。按照部门和职位对员工进行分组,并统计每个分组中的员工人数。
示例代码如下:
class Employee {
private String name;
private String department;
private String position;
public Employee(String name, String department, String position) {
this.name = name;
this.department = department;
this.position = position;
}
public String getName() {
return name;
}
public String getDepartment() {
return department;
}
public String getPosition() {
return position;
}
}
List<Employee> employees = Arrays.asList(
new Employee("Alice", "HR", "Manager"),
new Employee("Bob", "Engineering", "Developer"),
new Employee("Charlie", "HR", "Assistant"),
new Employee("David", "Engineering", "Tester"),
new Employee("Eve", "HR", "Manager")
);
Map<String, Map<String, Long>> result = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.groupingBy(
Employee::getPosition,
Collectors.counting()
)
));
System.out.println(result);flatMap 高级用法
示例,有一个包含多个班级的学校,每个班级又包含多个学生,每个学生有自己的课程列表。想要获取学校中所有学生的所有课程的去重列表。
示例代码如下:
class Course {
private String name;
public Course(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Student {
private String name;
private List<Course> courses;
public Student(String name, List<Course> courses) {
this.name = name;
this.courses = courses;
}
public List<Course> getCourses() {
return courses;
}
}
class Class {
private String name;
private List<Student> students;
public Class(String name, List<Student> students) {
this.name = name;
this.students = students;
}
public List<Student> getStudents() {
return students;
}
}
class School {
private List<Class> classes;
public School(List<Class> classes) {
this.classes = classes;
}
public List<Class> getClasses() {
return classes;
}
}
School school = new School(Arrays.asList(
new Class("Class1", Arrays.asList(
new Student("Alice", Arrays.asList(
new Course("Math"),
new Course("English")
)),
new Student("Bob", Arrays.asList(
new Course("Science"),
new Course("History")
))
)),
new Class("Class2", Arrays.asList(
new Student("Charlie", Arrays.asList(
new Course("Math"),
new Course("Art")
)),
new Student("David", Arrays.asList(
new Course("Music"),
new Course("PE")
))
))
));
Set<String> courses = school.getClasses().stream()
.flatMap(c -> c.getStudents().stream())
.flatMap(s -> s.getCourses().stream())
.map(Course::getName)
.collect(Collectors.toSet());
System.out.println(courses);reduce 灵活聚合
示例,有一个存储订单信息的列表,每个订单对象包含订单金额和订单日期等属性。找出订单金额最高的订单,并统计订单的总数量。
示例代码如下:
class Order {
private BigDecimal amount;
private LocalDate date;
public Order(BigDecimal amount, LocalDate date) {
this.amount = amount;
this.date = date;
}
public BigDecimal getAmount() {
return amount;
}
public LocalDate getDate() {
return date;
}
}
List<Order> orders = Arrays.asList(
new Order(new BigDecimal("100.50"), LocalDate.of(2023, 10, 1)),
new Order(new BigDecimal("200.75"), LocalDate.of(2023, 10, 2)),
new Order(new BigDecimal("150.25"), LocalDate.of(2023, 10, 3))
);
class OrderStats {
private Order highestAmountOrder;
private int count;
public OrderStats() {
this.highestAmountOrder = null;
this.count = 0;
}
public void update(Order order) {
if (highestAmountOrder == null || order.getAmount().compareTo(highestAmountOrder.getAmount()) > 0) {
highestAmountOrder = order;
}
count++;
}
public void combine(OrderStats other) {
if (other.highestAmountOrder != null && (highestAmountOrder == null || other.highestAmountOrder.getAmount().compareTo(highestAmountOrder.getAmount()) > 0)) {
highestAmountOrder = other.highestAmountOrder;
}
count += other.count;
}
public Order getHighestAmountOrder() {
return highestAmountOrder;
}
public int getCount() {
return count;
}
}
OrderStats stats = orders.stream()
.reduce(new OrderStats(),
(acc, order) -> {
acc.update(order);
return acc;
},
(acc1, acc2) -> {
acc1.combine(acc2);
return acc1;
});
System.out.println("订单金额最高的订单: " + stats.getHighestAmountOrder());
System.out.println("订单总数量: " + stats.getCount());现在还不熟悉Stream特性的程序员,不是好程序员QAQ。