|
|
@@ -445,12 +445,110 @@ public class SwimmingTest {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+### 函数式编程思想
|
|
|
+
|
|
|
+在数学中,函数就是"有输入量、输出量的一套计算方案",即**"拿数据做操作"**:
|
|
|
+
|
|
|
+- **面向对象思想**强调"必须通过对象的形式来做事情"
|
|
|
+- **函数式编程思想**则尽量忽略面向对象的复杂写法,强调"**做什么**"而不是"**以什么形式去做**"
|
|
|
+
|
|
|
+**Lambda 表达式就是体现函数式编程思想的一种实现方式。**
|
|
|
+
|
|
|
+### JDK 内置函数式接口(java.util.function 包)
|
|
|
+
|
|
|
+函数式接口:**接口中只包含一个抽象方法**的接口就是函数式接口。JDK 内置了大量函数式接口,基本都存在于 `java.util.function` 包中:
|
|
|
+
|
|
|
+| 接口 | 抽象方法形态 | 说明 |
|
|
|
+|------|--------------|------|
|
|
|
+| `Supplier<T>` | 无参有返回值 | 返回类型 T |
|
|
|
+| `Consumer<T>` | 有参无返回值 | 参数类型 T |
|
|
|
+| `Function<T, R>` | 有参有返回值 | 参数类型 T,返回类型 R |
|
|
|
+| `Predicate<T>` | 有参有返回值 | 参数类型 T,返回值 boolean(断言) |
|
|
|
+| `UnaryOperator<T>` | 有参有返回值 | 接收 T,返回 T(一元运算) |
|
|
|
+| `BinaryOperator<T>` | 有参有返回值 | 接收 T, T,返回 T(二元运算) |
|
|
|
+
|
|
|
+### Lambda 省略模式
|
|
|
+
|
|
|
+Demo04 中总结了三条**省略规则**:
|
|
|
+
|
|
|
+| 省略规则 | 说明 |
|
|
|
+|----------|------|
|
|
|
+| **① 参数类型可以省略** | 但不能"多个参数只省略其中一个"——要么全写,要么全省 |
|
|
|
+| **② 参数有且只有一个,小括号可以省略** | `(s) -> {...}` 可省略为 `s -> {...}` |
|
|
|
+| **③ 代码块的语句只有一条,可以省略大括号、分号以及 return 关键字** | `(a, b) -> { return a + b; }` 可省略为 `(a, b) -> a + b` |
|
|
|
+
|
|
|
+#### 省略模式示例一:有参有返回值接口 Addable
|
|
|
+
|
|
|
+```java
|
|
|
+// 来源:course/Addable.java + AddableDemo.java
|
|
|
+// 有参有返回值的函数式接口
|
|
|
+public interface Addable {
|
|
|
+ int add(int x, int y);
|
|
|
+}
|
|
|
+
|
|
|
+public class AddableDemo {
|
|
|
+ public static void useAdd(Addable a) {
|
|
|
+ int sum = a.add(10, 20);
|
|
|
+ System.out.println(sum);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void main(String[] args) {
|
|
|
+ // 完整写法:
|
|
|
+ // useAdd((a, b) -> {
|
|
|
+ // return a + b;
|
|
|
+ // });
|
|
|
+
|
|
|
+ // 省略写法:代码块只有一条语句 → 省略大括号、分号、return 关键字
|
|
|
+ useAdd((a, b) -> a + b); // 两个参数 → 小括号不能省略
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 省略模式示例二:有参无返回值接口 Flyable
|
|
|
+
|
|
|
+```java
|
|
|
+// 来源:course/Flyable.java + FlyableDemo.java
|
|
|
+// 有参无返回值的函数式接口
|
|
|
+public interface Flyable {
|
|
|
+ void fly(String s);
|
|
|
+}
|
|
|
+
|
|
|
+public class FlyableDemo {
|
|
|
+ public static void useFlyable(Flyable f) {
|
|
|
+ f.fly("今天天气不错...是风和日丽的...");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void main(String[] args) {
|
|
|
+ // 参数有且只有一个 → 小括号可以省略:s -> {...}
|
|
|
+ useFlyable(s -> {
|
|
|
+ System.out.println(s);
|
|
|
+ System.out.println("长翅膀....飞...");
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Lambda 使用前提
|
|
|
+
|
|
|
+使用 Lambda 表达式有两个**必须满足**的前提:
|
|
|
+1. **必须要有接口**——Lambda 的本质是简化接口的匿名实现
|
|
|
+2. **接口中有且只有一个抽象方法**——即必须是函数式接口
|
|
|
+
|
|
|
+### Lambda 与匿名内部类的区别
|
|
|
+
|
|
|
+| 对比维度 | 匿名内部类 | Lambda 表达式 |
|
|
|
+|----------|-----------|---------------|
|
|
|
+| **需要的类型** | 可以是**接口、抽象类、普通具体类** | **只能是接口** |
|
|
|
+| **使用限制** | 接口有多个抽象方法也能用 | **必须只有一个抽象方法**(函数式接口) |
|
|
|
+| **实现原理** | 编译后产生一个**独立的 `.class` 字节码文件** | 编译后**不产生单独的 `.class` 文件**,字节码在运行的时候**动态生成** |
|
|
|
+
|
|
|
### 关键理解
|
|
|
|
|
|
1. **Lambda = 匿名内部类的简化形式**:因为 `Swimming` 接口里**只有一个**需要重写的抽象方法,所以 `()` 能指向的就只有这一个抽象方法,`->` 指向的就是重写抽象方法的代码块内容。
|
|
|
2. **使用前提**:Lambda 只能简化**函数式接口**(只有一个抽象方法),多抽象方法接口用 Lambda 会编译报错。
|
|
|
3. **三要素对应**:`(形式参数)` → 重写方法的参数列表;`->` → 指向动作;`{代码块}` → 重写方法的方法体。
|
|
|
-4. **与匿名内部类的联系**:本节第 3 节 Comparator 正是用匿名内部类实现的,后续可以用 Lambda 进一步简化——如 `Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge())`。
|
|
|
+4. **省略模式记忆口诀**:类型可全省、单参可省括号、单语句可省大括号/分号/return。
|
|
|
+5. **与匿名内部类的联系**:本节第 3 节 Comparator 正是用匿名内部类实现的,后续可以用 Lambda 进一步简化——如 `Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge())`。
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -774,6 +872,19 @@ public class Student {
|
|
|
2. 为什么 Lambda 只能用在**只有一个抽象方法**的接口上?—— 只有一个待重写方法,`()` 才能唯一确定指向它
|
|
|
3. 本节第 3 节的 Comparator 匿名内部类能否改写成 Lambda?—— 可以,如 `(o1, o2) -> o1.getAge() - o2.getAge()`,后续 Stream 也会大量使用
|
|
|
|
|
|
+### 练习八:Lambda 省略模式与 JDK 内置函数式接口
|
|
|
+
|
|
|
+**核心步骤**:
|
|
|
+1. 定义**有参有返回值**函数式接口 `Addable`(`int add(int x, int y)`),用 Lambda 实现 `useAdd((a, b) -> a + b)`——验证"单语句省略大括号/分号/return"
|
|
|
+2. 定义**有参无返回值**函数式接口 `Flyable`(`void fly(String s)`),用 Lambda 实现 `s -> {...}`——验证"单参数省略小括号"
|
|
|
+3. 改写 Comparator:`Collections.sort(list, (s1, s2) -> s1.getAge() - s2.getAge())`,观察省略后的极简写法
|
|
|
+4. 查阅 `java.util.function` 包,说出 `Supplier / Consumer / Function / Predicate` 四个接口的形态
|
|
|
+
|
|
|
+**思考**:
|
|
|
+1. 三条省略规则分别是什么?—— 参数类型可全省(不能只省一个)、单参数可省小括号、单语句可省大括号/分号/return
|
|
|
+2. 为什么 `(a, b) -> a + b` 能省略 return?—— 代码块只有一条语句且是表达式时,表达式结果自动作为返回值
|
|
|
+3. Lambda 与匿名内部类的本质区别是什么?—— 匿名内部类编译产生独立 .class 文件;Lambda 编译不产生 .class,运行期动态生成字节码
|
|
|
+
|
|
|
### 随堂练习参考解答(已提供代码)
|
|
|
|
|
|
对应今日课堂练习的参考解答已写入授课代码:
|
|
|
@@ -854,6 +965,7 @@ treeSet = new TreeSet<>(new Comparator<Employee>() {
|
|
|
- [可变参数 Varargs(Oracle 教程)](https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html#varargs)
|
|
|
- [Lambda 表达式(Oracle 教程)](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html)
|
|
|
- [函数式接口(Oracle 教程)](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#functional)
|
|
|
+- [java.util.function 包 (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/package-summary.html)
|
|
|
- [RuntimeException (JDK 17)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html)
|
|
|
|
|
|
### 推荐阅读
|
|
|
@@ -861,12 +973,14 @@ treeSet = new TreeSet<>(new Comparator<Employee>() {
|
|
|
- 《Java 核心技术 卷 I》第 9 章 集合 —— 排序与比较器
|
|
|
- 《Effective Java》第 3 版 第 14 条:考虑实现 Comparable 接口
|
|
|
- 《Java 编程思想》第 17 章 容器深入研究 —— 哈希码与排序
|
|
|
+- 《Java 8 实战》第 3 章 Lambda 表达式 —— 行为参数化、函数式接口、方法引用
|
|
|
|
|
|
### 相关知识点
|
|
|
|
|
|
| 前置知识 | 当前知识 | 后续知识 |
|
|
|
|----------|----------|----------|
|
|
|
| 匿名内部类(0727) | Lambda 表达式 / 函数式接口 | Stream 流、方法引用 |
|
|
|
+| 接口与多态(0723/0724) | JDK 内置函数式接口(java.util.function) | 方法引用、Stream 高阶函数 |
|
|
|
| 集合体系与泛型(0729) | TreeSet / TreeMap 有序集合 | 红黑树底层(0731 已讲) |
|
|
|
| HashMap 基本操作(0731) | Map 综合练习(keySet/entrySet 遍历) | Map 与 Set 的互转、分组统计 |
|
|
|
| 异常处理(0728) | 自定义异常 + 多 catch 块 | 异常链、try-with-resources |
|