跳转至

16 Lambda 与 Stream

本章目标

  • 理解函数式接口
  • 掌握 Lambda 表达式写法
  • 掌握方法引用
  • 掌握 Stream API 的常用操作

16.1 函数式接口

只有一个抽象方法的接口,叫函数式接口(可以用 @FunctionalInterface 标记)。它的实现可以用 Lambda 简洁书写。

@FunctionalInterface
public interface Calculator {
    int calc(int a, int b);     // 只有一个抽象方法
}

16.2 Lambda 表达式

Lambda 就是"匿名函数"的简洁写法:(参数) -> 表达式/代码块

// 传统匿名类
Calculator add = new Calculator() {
    @Override
    public int calc(int a, int b) {
        return a + b;
    }
};

// Lambda
Calculator add = (a, b) -> a + b;

// 使用
System.out.println(add.calc(3, 4));   // 7

Lambda 各种写法

// 一个参数:可以省略括号
x -> x * 2

// 多个参数
(a, b) -> a + b

// 多条语句:用 {} 包起来
(a, b) -> {
    int sum = a + b;
    System.out.println("sum = " + sum);
    return sum;
}

// 无参数
() -> System.out.println("hello")

Java 内置函数式接口(java.util.function)

接口 抽象方法 用途
Function<T,R> R apply(T) 转换:输入 T 输出 R
Consumer<T> void accept(T) 消费:处理一个值无返回
Supplier<T> T get() 供应:无参返回一个值
Predicate<T> boolean test(T) 判断:返回布尔
Runnable void run() 无参无返回
Function<String, Integer> toLen = s -> s.length();
toLen.apply("hello");    // 5

Consumer<String> printer = s -> System.out.println(s);
printer.accept("你好");

Supplier<Double> random = () -> Math.random();
random.get();

Predicate<Integer> isEven = n -> n % 2 == 0;
isEven.test(4);          // true

16.3 排序的 Lambda 应用(模组常用)

List<Player> players = new ArrayList<>();
// 按等级降序
players.sort((p1, p2) -> p2.getLevel() - p1.getLevel());
// 按名字排序
players.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
// 按等级升序
players.sort(Comparator.comparingInt(Player::getLevel));

16.4 方法引用

方法已经存在时,直接用 类名::方法名 引用:

List<String> names = List.of("小明", "小红");

// Lambda
names.forEach(s -> System.out.println(s));
// 方法引用(等价)
names.forEach(System.out::println);

// 静态方法引用
Function<String, Integer> f = Integer::parseInt;   // 等价 s -> Integer.parseInt(s)

// 实例方法引用
names.sort(String::compareTo);

16.5 Stream API

Stream 是对集合的流水线式处理:创建流 → 中间操作(可多个)→ 终止操作。

List<Integer> numbers = List.of(5, 3, 8, 1, 9, 3, 2);

// 过滤偶数 -> 排序 -> 去重 -> 收集成列表
List<Integer> result = numbers.stream()
        .filter(n -> n % 2 == 0)      // 中间:过滤
        .sorted()                      // 中间:排序
        .distinct()                    // 中间:去重
        .toList();                     // 终止:收集
// [2, 8]

常用操作

// filter:过滤
players.stream().filter(p -> p.getLevel() >= 30)

// map:转换(提取字段)
List<String> names = players.stream()
        .map(Player::getName)
        .toList();

// forEach:遍历
players.stream().forEach(p -> System.out.println(p.getName()));

// count:计数
long count = players.stream().filter(p -> p.getHp() <= 0).count();

// anyMatch / allMatch / noneMatch:判断
boolean anyOp = players.stream().anyMatch(Player::isOp);

// findFirst:取第一个
Player first = players.stream().findFirst().orElse(null);

// reduce:聚合(求和)
int total = numbers.stream().reduce(0, Integer::sum);

// max / min
Optional<Integer> max = numbers.stream().max(Integer::compareTo);

// collect 到各种容器
List<Player> list = players.stream().filter(...).toList();
Set<String> set = players.stream().map(Player::getName).collect(Collectors.toSet());
Map<String, Integer> map = players.stream()
        .collect(Collectors.toMap(Player::getName, Player::getLevel));

Stream 不修改原集合

stream() 的操作产生新结果,原集合不变(除非你用 forEach 改对象内部)。

并行流

list.parallelStream() 多线程处理,数据量大时可能更快;但要注意线程安全,新手慎用。

16.6 实战:模组场景

// 从在线玩家中筛选出等级最高的 5 个管理员
List<Player> topOps = onlinePlayers.stream()
        .filter(Player::isOp)
        .sorted((p1, p2) -> p2.getLevel() - p1.getLevel())
        .limit(5)
        .toList();

// 统计掉落表总权重
double totalWeight = dropTable.stream()
        .mapToDouble(DropEntry::getWeight)
        .sum();

// 给所有怪物施加 3 秒缓慢效果(用 forEach)
mobs.stream().forEach(m -> m.addEffect(new MobEffectInstance(Effects.MOVEMENT_SLOWDOWN, 60, 1)));

16.7 小练习

  1. 用 Lambda 给玩家列表按血量升序排序
  2. 用 Stream:从 1~100 的 List 里筛出能被 3 整除的,求它们的平方和
  3. 用 map 把字符串列表转成大写列表
  4. 把玩家列表按等级分组(Collectors.groupingBy)

本章小结

  • 函数式接口 = 一个抽象方法的接口,可用 Lambda
  • Lambda 语法:(参数) -> 表达式
  • 方法引用 类::方法 更简洁
  • Stream 流水线:filter/map/sorted 中间操作 + toList/collect 终止操作
  • Stream 不修改原集合

下一章:17 多线程基础

🏠 返回总站