跳到内容

元数据卡

  • 前置知识:第8+章全章(Lambda、函数式接口、Stream API)
  • 预计时间:20 分钟
  • 阅读模式:动手实操
  • 完成标志:通过全部热身题,至少完成一个挑战题

你的进度

"光说不练,等于没学。"老陈师傅把一块刻着 Lambda 的羽毛拍在桌上。

你已经学完了函数式接口、Lambda 表达式、方法引用、Stream API。工具箱里有了这些新工具,该把它们用起来了。


热身(10 分钟,必做)

1. Stream 字符串处理

给你一个字符串列表,用 Stream 完成:

  • 过滤出长度大于 3 的
  • 全部转大写
  • 排序
  • 打印每个元素
java
List<String> words = List.of("猫", "老虎", "大象", "长颈鹿", "狗");

words.stream()
        .filter(w -> w.length() > 3)    // 过滤
        .map(String::toUpperCase)       // 转大写(方法引用!)
        .sorted()                        // 排序
        .forEach(System.out::println);   // 打印

语言:Java 8+ 预期输出大象长颈鹿

2. 匿名内部类 → Lambda

把下面这个匿名内部类改写成 Lambda:

java
// 改写前
Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("任务执行中");
    }
};

// 改写后
Runnable task = () -> System.out.println("任务执行中");

你试试:改成 Lambda 后,还能改成方法引用吗?

3. Lambda → 方法引用

把下面这个 Lambda 改写成方法引用:

java
Function<String, Integer> f = s -> Integer.parseInt(s);

// 改写后
Function<String, Integer> f = Integer::parseInt;

挑战(15 分钟,选做)

1. Stream 数据报告

给定一个 Villager 列表(字段:name, age, village),用 Stream API 完成:

java
record Villager(String name, int age, String village) {}

List<Villager> villagers = List.of(
    new Villager("张三", 25, "变量村"),
    new Villager("李四", 17, "变量村"),
    new Villager("王五", 30, "算法森林"),
    new Villager("赵六", 22, "变量村")
);

// 1. 找出所有来自"变量村"的村民
// 2. 按年龄排序
// 3. 只保留名字
// 4. 收集成 List<String>
// 5. 计算所有人的平均年龄

List<String> names = villagers.stream()
        .filter(v -> v.village().equals("变量村"))
        .sorted(Comparator.comparingInt(Villager::age))
        .map(Villager::name)
        .collect(Collectors.toList());

double avgAge = villagers.stream()
        .mapToInt(Villager::age)
        .average()
        .orElse(0.0);

你试试:想想如果只用 stream() 一次完成(不重复遍历),应该怎么做?提示:用 collect(Collectors.teeing(...)) 或用两次遍历。

2. 自定义函数式接口

写一个 Transformer<T, R> 接口,用 @FunctionalInterface 注解,然后创建一个数组,每个元素是一个不同的转换 Lambda,循环调用所有转换:

java
@FunctionalInterface
interface Transformer<T, R> {
    R transform(T input);
}

// 使用
Transformer<String, Integer> toLength = s -> s.length();
Transformer<String, String> toUpper = String::toUpperCase;
Transformer<String, String> withPrefix = s -> "[消息] " + s;

List.of(toLength, toUpper, withPrefix).forEach(t -> {
    System.out.println(t.transform("hello"));
});

验收标准

  • 能写出 Lambda 表达式(无参、单参、多参)
  • 能解释"函数式接口"和 Lambda 的关系——插座与插头
  • 能用方法引用替换简单的 Lambda:System.out::printlnString::lengthInteger::parseInt
  • 能用 Stream API 完成"过滤-转换-排序-收集"四步流水线
  • 能区分中间操作和终端操作

常见卡点

"Lambda 和匿名内部类一样吗?" 不一样。Lambda 底层用 invokedynamic 指令实现,不是生成匿名类。性能更好。

"为什么 Lambda 里的 this 指向外部类?" 因为 Lambda 没有自己的作用域——它和包围它的代码共享同一个 this。匿名内部类是一个独立的类,它的 this 就是它自己的实例。

"Stream 和集合有什么区别?"

  • 集合存储数据,Stream 不存储——它只是数据的"视图"
  • 集合可以被多次遍历,Stream 只能消费一次
  • Stream 是懒执行的——中间操作只在终端操作触发时才真正计算

旅人笔记

Lambda 把行为装进信封传给别人。 方法引用让 Lambda 再短一步。 Stream 流水线 = filter → map → sorted → collect。 四个核心:Supplier 生产值,Consumer 消费值,Function 转换值,Predicate 判断值。

下一站预告

你已经在"变量村"学完了编程的骨架知识——变量、控制流、方法、类、继承、Lambda。

第9章你会学到最常用的工具类:字符串怎么就不可变了?为什么不能直接拼 + 来造长字符串?枚举和 record 为什么能省那么多代码?这是你在新手村的最后一站——之后就要走进算法森林

Built with VitePress | Software Systems Atlas