Skip to content
第 24 章 后端 ⏱ 14 分钟阅读

第 24 章:Lambda 与 Stream 实战 ​

学习目标 ​

  • 会写 Lambda 表达式
  • 掌握 Stream 链式操作
  • 用 Stream 重写集合的常见处理

一、Lambda 表达式 ​

1.1 为什么要用 Lambda? ​

Lambda = 匿名函数的简写。以前写一个线程:

java
// ❌ 啰嗦:匿名内部类
new Thread(new Runnable() {
    @Override public void run() {
        System.out.println("running");
    }
}).start();

// ✅ 一行 Lambda
new Thread(() -> System.out.println("running")).start();

1.2 三种写法 ​

java
// ① 无参,() 不能省
Runnable r = () -> System.out.println("hi");

// ② 一个参数,类型可省
Consumer<String> c = s -> System.out.println(s);

// ③ 多个参数 + 方法体多行
BinaryOperator<Integer> add = (a, b) -> {
    int sum = a + b;
    return sum * 2;
};

⚠️ 坑 1:单参数才可以省略括号,无参必须有 (),多参必须有 (a, b)。

java
() -> 1            // ✅
x -> x * 2         // ✅
(x, y) -> x + y    // ✅
x, y -> x + y      // ❌

1.3 方法引用(:😃 ​

当 Lambda 只是"调用一个现成方法"时,用 :: 更简洁:

java
// Lambda
list.forEach(s -> System.out.println(s));

// 方法引用(等价)
list.forEach(System.out::println);

四种方法引用:

java
// ① 静态方法
Integer::parseInt                       // s -> Integer.parseInt(s)

// ② 实例方法(已知对象)
System.out::println                     // s -> System.out.println(s)

// ③ 实例方法(未知对象,第一个参数是调用者)
String::length                          // s -> s.length()

// ④ 构造方法
ArrayList::new                          // () -> new ArrayList()

二、函数式接口(知道几个就够) ​

函数式接口 = 只有一个抽象方法的接口,Lambda 才能赋值给它。

java
@FunctionalInterface
interface MyFunc {
    void run();    // 只有一个抽象方法
}

JDK 自带的常用接口(都在 java.util.function):

接口签名用途
Supplier<T>T get()无参,有返回值(工厂)
Consumer<T>void accept(T)有参,无返回值(消费)
Function<T,R>R apply(T)有参,有返回值(转换)
Predicate<T>boolean test(T)判断(过滤)
Runnablevoid run()无参无返回值(线程)

⚠️ 坑 2:别自己造函数式接口,直接用 JDK 的 Supplier / Consumer / Function / Predicate。

三、Stream 是什么 ​

Stream = 用链式 API 处理集合。比 for 循环更简洁。

java
// ❌ 传统写法
List<String> result = new ArrayList<>();
for (User u : users) {
    if (u.getAge() >= 18) {
        result.add(u.getName().toUpperCase());
    }
}

// ✅ Stream 写法
List<String> result = users.stream()
    .filter(u -> u.getAge() >= 18)
    .map(User::getName)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

四、Stream 操作分两类 ​

类型方法返回值特点
中间操作filter / map / sorted / distinctStream链式调用,懒执行
终端操作collect / forEach / count / reduce非 Stream触发真正执行
java
// filter + map + collect:经典三连
List<String> names = users.stream()
    .filter(u -> u.getActive())                              // 过滤
    .map(u -> u.getName())                                  // 转换
    .sorted()                                               // 排序
    .collect(Collectors.toList());                          // 收集

⚠️ 坑 3:Stream 只能消费一次,用了 collect 之后这个 Stream 就废了。

java
Stream<String> s = list.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);   // ❌ IllegalStateException

五、常用操作大全 ​

5.1 过滤 filter ​

java
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
List<Integer> even = nums.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());   // [2, 4, 6]

5.2 转换 map ​

java
List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

5.3 排序 sorted ​

java
// 自然顺序
nums.stream().sorted().forEach(System.out::println);

// 自定义排序
users.stream()
    .sorted(Comparator.comparing(User::getAge).reversed())  // 按年龄倒序
    .forEach(System.out::println);

5.4 去重 distinct ​

java
List<Integer> uniq = List.of(1, 2, 2, 3, 3).stream()
    .distinct()
    .collect(Collectors.toList());   // [1, 2, 3]

5.5 截取 limit / skip ​

java
// 取前 10
List<User> top10 = users.stream().limit(10).collect(Collectors.toList());

// 跳过前 10,取剩下的
List<User> others = users.stream().skip(10).collect(Collectors.toList());

5.6 遍历 forEach ​

java
list.stream().forEach(System.out::println);

5.7 统计 ​

java
long count = list.stream().count();
Optional<Integer> max = list.stream().max(Integer::compareTo);
Optional<Integer> min = list.stream().min(Integer::compareTo);
boolean any = list.stream().anyMatch(n -> n > 100);   // 任一满足
boolean all = list.stream().allMatch(n -> n > 0);     // 全部满足

5.8 聚合 reduce ​

java
// 求和
int sum = nums.stream().reduce(0, Integer::sum);

// 字符串拼接
String joined = List.of("a", "b", "c").stream()
    .reduce("", (a, b) -> a + b);   // "abc"

⚠️ 坑 4:Optional 是为了防空指针,别用 if (opt.isPresent()) 然后 get(),直接用 orElse。

java
// ❌ 啰嗦
Integer max = list.stream().max(Integer::compareTo).isPresent()
    ? list.stream().max(Integer::compareTo).get() : 0;
// ✅ 简洁
Integer max = list.stream().max(Integer::compareTo).orElse(0);

六、collect 收集器(最常用) ​

6.1 基本收集 ​

java
// 收集为 List
List<String> list = stream.collect(Collectors.toList());

// 收集为 Set
Set<String> set = stream.collect(Collectors.toSet());

// 收集为 Map(key 不能重复!)
Map<Long, User> map = users.stream()
    .collect(Collectors.toMap(User::getId, u -> u));   // id -> User

6.2 分组 groupingBy ​

java
// 按部门分组
Map<String, List<User>> byDept = users.stream()
    .collect(Collectors.groupingBy(User::getDept));

// 按部门统计人数
Map<String, Long> countByDept = users.stream()
    .collect(Collectors.groupingBy(User::getDept, Collectors.counting()));

6.3 分区 partitioningBy(只有 true/false 两组) ​

java
Map<Boolean, List<User>> parts = users.stream()
    .collect(Collectors.partitioningBy(u -> u.getAge() >= 18));
// parts.get(true)  -> 成年
// parts.get(false) -> 未成年

6.4 拼接 joining ​

java
String joined = users.stream()
    .map(User::getName)
    .collect(Collectors.joining(", "));   // "Alice, Bob, Charlie"

七、实战案例 ​

7.1 找出年龄最大的 3 个用户 ​

java
List<User> top3 = users.stream()
    .sorted(Comparator.comparing(User::getAge).reversed())
    .limit(3)
    .collect(Collectors.toList());

7.2 统计每个城市的用户平均年龄 ​

java
Map<String, Double> avgAge = users.stream()
    .collect(Collectors.groupingBy(
        User::getCity,
        Collectors.averagingInt(User::getAge)
    ));

7.3 把 List 转 Map(处理 key 重复) ​

java
// ❌ key 重复会抛 IllegalStateException
Map<Long, User> map = users.stream()
    .collect(Collectors.toMap(User::getId, u -> u));

// ✅ 重复时取后者
Map<Long, User> map = users.stream()
    .collect(Collectors.toMap(
        User::getId,
        u -> u,
        (existing, replacement) -> replacement));

⚠️ 坑 5:Collectors.toMap 默认不允许 key 重复,生产中一定要传 mergeFunction。

八、并行 Stream(慎用) ​

java
list.parallelStream().forEach(System.out::println);

parallel 不一定快:

  • ✅ 数据量大、每步独立(如统计、求和)才用
  • ❌ 小数据(< 1000)反而更慢(线程切换开销)
  • ❌ 涉及共享变量、有顺序要求时不能用

⚠️ 坑 6:并行 Stream 内部用 ForkJoinPool.commonPool,全局共享,不要在并行情境下阻塞。

九、本章小结 ​

要点关键
Lambda(参数) -> { 方法体 },单参数可省括号
方法引用类名::方法名,Lambda 只调一个方法时用
Stream链式操作,中间操作 + 终端操作
常用操作filter / map / sorted / collect
收集器toList / toMap / groupingBy / joining
Optional用 orElse / orElseGet,别 isPresent + get
并行 Stream数据量大 + 独立操作才用

动手练习 ​

  1. Lambda 改写:把项目里所有匿名内部类改成 Lambda
  2. 过滤 + 排序:100 个用户,过滤年龄 > 18,按名字排序,取前 10
  3. 分组统计:用 groupingBy 统计每个部门的人数和平均薪资
  4. List 转 Map:把 List<User> 转成 id -> User 的 Map,处理 key 重复

下一章:第 25 章:反射基础 →

本站基于 VitePress 构建 · 由 StackHub 团队维护