|
|
@@ -267,7 +267,185 @@ public class UserService extends BaseService {
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 三、随堂练习要点
|
|
|
+## 三、final 关键字
|
|
|
+
|
|
|
+### 1. final 的引出
|
|
|
+
|
|
|
+**文件**:`Demo02.java`
|
|
|
+
|
|
|
+在实际开发中,有些类、方法或变量不希望被改变:
|
|
|
+- 某个工具类,只能被调用,不希望被继承
|
|
|
+- 某些核心方法,不希望被子类重写(篡改)
|
|
|
+- 某些固定值,希望成为常量,不允许修改
|
|
|
+
|
|
|
+`final` 关键字就是用来解决这些问题的——**用于限制类、方法或变量被修改**。
|
|
|
+
|
|
|
+### 2. final 修饰类——禁止继承
|
|
|
+
|
|
|
+**文件**:`Demo02.java`、`FinalClass.java`
|
|
|
+
|
|
|
+```java
|
|
|
+// 使用 final 修饰的类,不能被继承
|
|
|
+public final class 类名 {
|
|
|
+ // ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**应用场景**:
|
|
|
+- 工具类(如 `Math`、`Arrays`),只能被调用,不能被继承
|
|
|
+- 特殊功能的类,如 `String` 类就是用 `final` 修饰的,不能被继承
|
|
|
+
|
|
|
+### 3. final 修饰方法——禁止重写
|
|
|
+
|
|
|
+**文件**:`FinalClass.java`、`SubClass.java`
|
|
|
+
|
|
|
+```java
|
|
|
+// 父类中的 final 方法
|
|
|
+public class FinalClass {
|
|
|
+ final String SPRING = "春天";
|
|
|
+
|
|
|
+ public final void showInfo() {
|
|
|
+ System.out.printf(SPRING);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 子类无法重写父类的 final 方法
|
|
|
+public class SubClass extends FinalClass {
|
|
|
+ // ❌ 编译报错:不能重写父类的 final 方法
|
|
|
+ // public final void showInfo() {
|
|
|
+ // System.out.printf(SPRING);
|
|
|
+ // }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- `FinalClass` 类本身没有用 `final` 修饰,所以 `SubClass` 可以继承它
|
|
|
+- 但 `FinalClass` 中的 `showInfo()` 方法用 `final` 修饰,子类 **不能重写** 该方法
|
|
|
+- 注掉了 `SubClass` 中的重写代码,否则编译报错
|
|
|
+
|
|
|
+### 4. final 修饰变量——变成常量
|
|
|
+
|
|
|
+**文件**:`Demo02.java`、`Test02.java`
|
|
|
+
|
|
|
+```java
|
|
|
+// final 修饰基本类型变量——值不可修改
|
|
|
+final int i = 10;
|
|
|
+// i = 20; // ❌ 编译报错:不能修改 final 变量的值
|
|
|
+
|
|
|
+// final 修饰引用类型变量——引用地址不可变,但对象内容可以修改
|
|
|
+final Person p = new Person("1001", "张三");
|
|
|
+// p = new Person("1002", "李四"); // ❌ 编译报错:不能修改引用地址
|
|
|
+p.setName("李四"); // ✅ 可以修改对象的属性
|
|
|
+p.setId("1002");
|
|
|
+System.out.printf(p.showInfo()); // 输出:Person{id='1002', name='李四'}
|
|
|
+```
|
|
|
+
|
|
|
+**关键区别**:
|
|
|
+
|
|
|
+| 变量类型 | final 的作用 | 示例 |
|
|
|
+|---------|-------------|------|
|
|
|
+| **基本类型**(int、double 等) | 数值本身不可修改 | `final int i = 10;` → `i=20;` 报错 |
|
|
|
+| **引用类型**(对象、数组等) | 引用地址不可修改,但对象 **属性** 可修改 | `final Person p = new Person(...)` → `p.setName(...)` 可以 |
|
|
|
+
|
|
|
+> **注意**:`final` 修饰引用类型变量时,只是保证这个变量不再指向其他对象,但该对象内部的 **成员变量** 仍然可以通过 setter 修改。
|
|
|
+
|
|
|
+### 5. final 与 abstract 的对比
|
|
|
+
|
|
|
+| 关键字 | 修饰类 | 修饰方法 | 修饰变量 |
|
|
|
+|--------|-------|---------|---------|
|
|
|
+| **`abstract`** | 类必须被继承 | 方法必须在子类中实现(无方法体) | 不能修饰变量 |
|
|
|
+| **`final`** | 类不能被继承 | 方法不能被重写 | 变量变成常量 |
|
|
|
+
|
|
|
+> ⚠️ **`final` 和 `abstract` 不能同时使用**,二者语义完全冲突:
|
|
|
+> - `abstract` 表示「需要被继承/重写」
|
|
|
+> - `final` 表示「不能被继承/重写」
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、随堂练习代码示例——Shape 抽象类(计算周长 + 计算面积)
|
|
|
+
|
|
|
+> 代码位于 `exercise/exercise02` 包中,对应课堂练习题第 2 题。
|
|
|
+
|
|
|
+### 抽象父类 Shape
|
|
|
+
|
|
|
+**文件**:`exercise/exercise02/Shape.java`
|
|
|
+
|
|
|
+```java
|
|
|
+package exercise.exercise02;
|
|
|
+
|
|
|
+public abstract class Shape {
|
|
|
+ private String color;
|
|
|
+
|
|
|
+ public Shape(String color) {
|
|
|
+ this.color = color;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 计算面积——抽象方法 */
|
|
|
+ public abstract double getArea();
|
|
|
+
|
|
|
+ /** 计算周长——抽象方法 */
|
|
|
+ public abstract double getPerimeter();
|
|
|
+
|
|
|
+ /** 输出信息——普通方法(调用抽象方法) */
|
|
|
+ public void showInfo() {
|
|
|
+ System.out.print("颜色:" + this.color);
|
|
|
+ System.out.printf(",面积:%.2f", getArea());
|
|
|
+ System.out.printf(",周长:%.2f", getPerimeter());
|
|
|
+ System.out.println();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- 定义了两个抽象方法:`getArea()` 和 `getPerimeter()`
|
|
|
+- 定义了一个普通方法 `showInfo()`,内部调用抽象方法输出结果
|
|
|
+- 体现了 **模板方法** 的思想——父类定义输出框架,子类提供具体的面积和周长计算
|
|
|
+
|
|
|
+### 子类 Circle
|
|
|
+
|
|
|
+**文件**:`exercise/exercise02/Circle.java`
|
|
|
+
|
|
|
+```java
|
|
|
+package exercise.exercise02;
|
|
|
+
|
|
|
+public class Circle extends Shape {
|
|
|
+ private double radius;
|
|
|
+ private double pi = 3.1415926;
|
|
|
+
|
|
|
+ public Circle(String color, double radius) {
|
|
|
+ super(color); // 调用父类构造方法
|
|
|
+ this.radius = radius; // 初始化子类属性
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public double getArea() {
|
|
|
+ return pi * radius * radius;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public double getPerimeter() {
|
|
|
+ return 2 * pi * radius;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 测试类 Test
|
|
|
+
|
|
|
+**文件**:`exercise/exercise02/Test.java`
|
|
|
+
|
|
|
+```java
|
|
|
+package exercise.exercise02;
|
|
|
+
|
|
|
+public class Test {
|
|
|
+ public static void main(String[] args) {
|
|
|
+ Circle circle = new Circle("红色", 5);
|
|
|
+ circle.showInfo();
|
|
|
+ // 输出:颜色:红色,面积:78.54,周长:31.42
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、随堂练习要点
|
|
|
|
|
|
**练习目标**:理解抽象类的定义和使用
|
|
|
|
|
|
@@ -277,7 +455,7 @@ public class UserService extends BaseService {
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 四、面试常见问题
|
|
|
+## 六、面试常见问题
|
|
|
|
|
|
**Q1:抽象类能不能被实例化?**
|
|
|
> 不能。抽象类不能直接 `new` 创建对象。
|
|
|
@@ -294,9 +472,15 @@ public class UserService extends BaseService {
|
|
|
**Q5:`abstract` 和 `final` 能否同时修饰一个类或方法?**
|
|
|
> 不能。`abstract` 表示需要被继承/重写,`final` 表示不能被继承/重写,二者语义冲突。
|
|
|
|
|
|
+**Q6:`final` 修饰引用类型变量时,对象内容还能修改吗?**
|
|
|
+> 可以。`final` 修饰引用类型变量时,只是保证引用地址不变(不能重新指向其他对象),但该对象内部的成员变量仍然可以通过 setter 修改。
|
|
|
+
|
|
|
+**Q7:`final` 修饰的类有哪些常见例子?**
|
|
|
+> `String` 类、`Math` 类、`Integer` 等包装类都是用 `final` 修饰的,它们不能被继承。
|
|
|
+
|
|
|
---
|
|
|
|
|
|
-## 五、拓展阅读
|
|
|
+## 七、拓展阅读
|
|
|
|
|
|
- [Java 抽象类(Oracle 官方文档)](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html)
|
|
|
- 抽象类 vs 接口(Interface)的区别(将在后续课程中讲解)
|