Stream 常用操作
掌握了核心概念后,这一节聚焦常用操作的实战用法。每个操作都有代码示例,直接复制能用。
筛选类操作
filter — 按条件过滤
java
// 基本用法
List<String> longNames = names.stream()
.filter(name -> name.length() > 5)
.toList();
// 组合条件
List<Integer> evens = numbers.stream()
.filter(n -> n > 0 && n % 2 == 0)
.toList();
// 结合 Predicate
Predicate<String> notEmpty = s -> !s.isBlank();
Predicate<String> isLong = s -> s.length() > 3;
List<String> filtered = names.stream()
.filter(notEmpty.and(isLong))
.toList();distinct — 去重
java
// 基本去重(依赖 equals)
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> distinct = numbers.stream()
.distinct()
.toList();
// 按属性去重
List<Person> uniqueByName = people.stream()
.collect(Collectors.toMap(
Person::getName, // key 用来去重
p -> p, // value
(p1, p2) -> p1 // 冲突时保留第一个
))
.values()
.stream()
.toList();limit & skip — 分页
java
// 分页:第 n 页,每页 size 条
int page = 3, size = 10;
List<T> paged = list.stream()
.skip((long) (page - 1) * size)
.limit(size)
.toList();
// 随机取样
Random random = new Random(42);
List<Integer> sample = IntStream.range(0, 1000)
.boxed()
.sorted(() -> random.nextInt())
.limit(10)
.toList();转换类操作
map — 类型转换
java
// 属性提取
List<Integer> ages = people.stream()
.map(Person::getAge)
.toList();
// 转换类型
List<String> asStrings = numbers.stream()
.map(String::valueOf)
.toList();
// 一对多转换
List<String> words = Arrays.asList("hello", "world");
List<Character> chars = words.stream()
.flatMap(w -> w.chars()
.mapToObj(c -> (char) c))
.toList();
// [h, e, l, l, o, w, o, r, l, d]
// 转基本类型(避免装箱)
int totalLength = names.stream()
.mapToInt(String::length)
.sum();sorted — 排序
java
// 自然排序
List<String> sorted = names.stream()
.sorted()
.toList();
// 自定义排序
List<String> byLength = names.stream()
.sorted(Comparator.comparingInt(String::length))
.toList();
// 降序
List<String> desc = names.stream()
.sorted(Comparator.reverseOrder())
.toList();
// 按属性排序
List<Person> byAge = people.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.toList();
// 多条件排序
List<Person> complex = people.stream()
.sorted(Comparator
.comparing(Person::getDepartment)
.thenComparingInt(Person::getAge)
.thenComparing(Person::getName))
.toList();收集类操作
toList / toSet — 收集到列表或集合
java
// toList() — JDK 16+
List<String> result = stream.filter(...).toList();
// toSet()
Set<String> unique = stream.filter(...).collect(Collectors.toSet());
// toCollection — 指定具体类型
LinkedList<String> linked = stream
.filter(...)
.collect(Collectors.toCollection(LinkedList::new));
TreeSet<String> sortedSet = stream
.filter(...)
.collect(Collectors.toCollection(TreeSet::new));toMap — 收集到 Map
java
// 基本用法
Map<String, Integer> nameToAge = people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAge
));
// 冲突处理(key 重复时)
Map<String, Integer> nameToAge = people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAge,
(existing, newValue) -> existing // 保留旧的
));
// 指定 Map 类型
Map<String, Integer> nameToAge = people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAge,
Integer::sum,
TreeMap::new
));groupingBy — 分组
java
// 按单字段分组
Map<String, List<Person>> byDepartment = people.stream()
.collect(Collectors.groupingBy(Person::getDepartment));
// 分组后计数
Map<String, Long> countByDept = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.counting()
));
// 分组后求和/平均值
Map<String, Double> avgAgeByDept = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.averagingInt(Person::getAge)
));
// 多级分组
Map<String, Map<String, List<Person>>> byDeptAndCity = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.groupingBy(Person::getCity)
));
// 分组后取 Top N
Map<String, List<Person>> topByDept = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparingInt(Person::getAge).reversed())
.limit(3)
.toList()
)
));partitioningBy — 分区
java
// 按条件分为两组(true/false)
Map<Boolean, List<Person>> byAdult = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() > 18));
List<Person> adults = byAdult.get(true);
List<Person> minors = byAdult.get(false);
// 配合其他收集器
Map<Boolean, Long> countByAdult = people.stream()
.collect(Collectors.partitioningBy(
p -> p.getAge() > 18,
Collectors.counting()
));joining — 字符串拼接
java
// 逗号分隔
String joined = names.stream()
.collect(Collectors.joining(", "));
// Alice, Bob, Charlie
// 加前后缀
String withBracket = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
// [Alice, Bob, Charlie]
// 配合 map
String upperJoined = names.stream()
.map(String::toUpperCase)
.collect(Collectors.joining(" | "));
// ALICE | BOB | CHARLIE聚合类操作
count / min / max
java
// 计数
long count = stream.count();
// 最大/最小
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
// 配合 map
OptionalInt maxAge = people.stream()
.mapToInt(Person::getAge)
.max();
int maxAgeOrZero = people.stream()
.mapToInt(Person::getAge)
.max()
.orElse(0);sum / average
java
// 求和
int totalAge = people.stream()
.mapToInt(Person::getAge)
.sum();
// 平均值
double avgAge = people.stream()
.mapToInt(Person::getAge)
.average()
.orElse(0.0);
// 统计对象
IntSummaryStatistics stats = people.stream()
.mapToInt(Person::getAge)
.summaryStatistics();
System.out.println("平均: " + stats.getAverage());
System.out.println("最大: " + stats.getMax());
System.out.println("最小: " + stats.getMin());
System.out.println("总计: " + stats.getCount());reduce — 归约
java
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
// 求和
int sum = nums.stream().reduce(0, Integer::sum);
// 最大值
int max = nums.stream().reduce(Integer.MIN_VALUE, Integer::max);
// 字符串拼接
String concat = names.stream()
.reduce("", (a, b) -> a + b + ", ");
// 求积
int product = nums.stream().reduce(1, (a, b) -> a * b);查找类操作
java
// findFirst / findAny
Optional<String> first = stream.filter(...).findFirst();
Optional<String> any = stream.filter(...).findAny(); // 并行时更快
// anyMatch / allMatch / noneMatch
boolean hasPositive = numbers.stream().anyMatch(n -> n > 0);
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);实战组合
常见数据处理管道
java
// 场景:从用户列表中,按部门统计平均年龄,只保留平均年龄 > 30 的部门
Map<String, Double> result = users.stream()
.collect(Collectors.groupingBy(
User::getDepartment,
Collectors.averagingInt(User::getAge)
))
.entrySet().stream()
.filter(e -> e.getValue() > 30)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// 场景:找出每个部门年龄最大的两个人
Map<String, List<Employee>> topTwoByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparingInt(Employee::getAge).reversed())
.limit(2)
.toList()
)
));
// 场景:扁平化处理后分组
List<Order> orders = ...;
Map<String, List<OrderItem>> itemsByCategory = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(OrderItem::getCategory));小结
日常开发中,80% 的 Stream 代码其实只用到了以下组合:
| 场景 | 常用组合 |
|---|---|
| 过滤 + 收集到 List | filter(...).toList() |
| 按属性分组 | groupingBy(Class::getField) |
| 分组 + 统计数量 | groupingBy(... , counting()) |
| 分组 + 求和/平均 | groupingBy(... , summingInt(...)) |
| 去重 | distinct() |
| 排序 + 取 Top N | sorted().limit(n) |
| 提取属性 | map(Class::getField) |
记住这几个固定搭配,代码写起来飞快。
