Skip to content

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 代码其实只用到了以下组合:

场景常用组合
过滤 + 收集到 Listfilter(...).toList()
按属性分组groupingBy(Class::getField)
分组 + 统计数量groupingBy(... , counting())
分组 + 求和/平均groupingBy(... , summingInt(...))
去重distinct()
排序 + 取 Top Nsorted().limit(n)
提取属性map(Class::getField)

记住这几个固定搭配,代码写起来飞快。

基于 VitePress 构建