# JavaSE 基础 — Day09 笔记 > **日期**:2026-07-23 > **项目**:`c260723` > **作者**:WanJL --- ## 一、抽象类(Abstract Class) ### 1. 抽象类的引出 **文件**:`Demo01.java` 在父子类继承时,提取子类的共性功能时,有些方法在父类中并没有具体的实现。 例如员工体系: ``` 员工类 |- 开发工程师 |- Java开发工程师 |- 前端开发工程师 |- 测试工程师 |- 后端测试工程师 |- IOS测试工程师 ``` 每一个子类的工作内容都是不一样的,所以都需要重写父类的 `work()` 方法,导致父类的 `work()` 方法代码没有实际作用。 ```java public class 员工 { public void work() { // 每一个子类的工作内容都不一样,此方法体没有意义 } } ``` 在 Java 中,一个没有方法体的方法,应该定义为 **抽象方法**;如果类中包含抽象方法,那么这个类必须是 **抽象类**。 ### 2. 抽象类的定义 **文件**:`Demo01.java` 使用 `abstract` 关键字来定义抽象类和抽象方法。 **定义抽象类**: ```java public abstract class 类名 { // 成员 } ``` **定义抽象方法**: ```java public abstract 返回值类型 方法名(参数列表); ``` > **注意**:抽象方法没有方法体(没有 `{}`,直接以分号结尾)。 ### 3. 抽象类的特点 **文件**:`Demo01.java` | 特性 | 说明 | |------|------| | **关键字** | 抽象类和抽象方法必须使用 `abstract` 关键字修饰 | | **抽象方法所在类** | 有抽象方法的类必须是抽象类 | | **抽象类中的抽象方法** | 抽象类中不一定要有抽象方法,但抽象方法必须在抽象类中 | | **实例化** | **抽象类不能实例化(不能创建对象)** | | **构造方法** | 抽象类可以有构造方法(供子类通过 `super()` 调用) | | **子类选择** | ① 重写父类所有的抽象方法;② 子类也是抽象类,直接继承父类的抽象方法 | | **继承关系** | 抽象父类和具体子类之间是继承关系,子类仍然属于父类类型 | --- ## 二、抽象类代码示例 ### 示例 1:动物体系(Animal → Cat / Dog) #### 抽象父类 Animal **文件**:`Animal.java` ```java package course; public abstract class Animal { public Animal() { System.out.println("调用动物的构造方法...."); } public abstract void eat(); public abstract void sleep(); public abstract void move(); public abstract void play(); } ``` - 定义了 4 个抽象方法:`eat()`、`sleep()`、`move()`、`play()` - 包含一个构造方法,虽然抽象类不能直接创建对象,但构造方法可以通过子类 `super()` 调用 #### 子类 Cat **文件**:`Cat.java` ```java package course; public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } @Override public void sleep() { // 空实现 } @Override public void move() { // 空实现 } @Override public void play() { // 空实现 } } ``` - 使用 `extends` 继承抽象类 `Animal` - 使用 `@Override` 注解重写父类的所有抽象方法(必须全部重写,否则子类也要声明为抽象类) #### 子类 Dog **文件**:`Dog.java` ```java package course; public class Dog extends Animal { public void eat() { System.out.println("狗吃肉"); } @Override public void sleep() { } @Override public void move() { } @Override public void play() { } } ``` - 同样继承 `Animal`,重写所有抽象方法 - `eat()` 方法没有写 `@Override` 注解,但仍然是对父类抽象方法的重写(注解只是编译检查,不写也能重写) #### 测试类 Test01 **文件**:`Test01.java` ```java package course; public class Test01 { public static void main(String[] args) { Dog dog = new Dog(); dog.eat(); // 输出:狗吃肉 } } ``` - 创建 `Dog` 对象,调用重写后的 `eat()` 方法 - 验证了抽象类的子类可以正常创建对象和使用成员方法 --- ### 示例 2:员工体系(Emp → javaEmp) #### 抽象父类 Emp **文件**:`Emp.java` ```java package course; // 有抽象方法的类必须是抽象类 public abstract class Emp { /** * 抽象方法——没有方法体 */ public abstract void work(); } ``` - 只定义了一个抽象方法 `work()` - 类必须使用 `abstract` 修饰 #### 子类 javaEmp **文件**:`javaEmp.java` ```java package course; public class javaEmp extends Emp { @Override public void work() { System.out.println("Java开发工程师在开发Java项目"); } } ``` - 重写父类的 `work()` 抽象方法,给出具体的实现 - 不同子类可以提供不同的实现(例如还可以有 `前端开发工程师`、`后端测试工程师` 等) --- ### 示例 3:服务层体系(BaseService → UserService) #### 抽象父类 BaseService **文件**:`BaseService.java` ```java package course; public abstract class BaseService { public abstract void add(); public abstract void remove(); public abstract void update(); public abstract void query(); } ``` - 定义了标准的 CRUD(增删改查)四个抽象方法 - 在实际项目中常用来定义业务层接口规范 #### 子类 UserService **文件**:`UserService.java` ```java package course; public class UserService extends BaseService { @Override public void add() { } @Override public void remove() { } @Override public void update() { } @Override public void query() { } } ``` - 重写父类的所有抽象方法 - 方法体暂时为空,后续可添加具体的业务逻辑 --- ## 三、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='李四'} ``` **常量命名规范**:Java 中一般使用 `final` 修饰的变量作为常量,常量名称通常 **全部大写**,单词之间用下划线 `_` 分隔。例如: ```java final String SPRING = "春天"; // FinalClass.java final int MAX_STUDENT_COUNT = 100; final double PI = 3.1415926; ``` **关键区别**: | 变量类型 | 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 } } ``` --- ## 五、模板方法设计模式(Template Method Pattern) ### 1. 模板方法的引出 **文件**:`Demo03.java` **模板方法模式**是抽象类的经典应用场景。核心思想:**父类定义算法的「骨架」(通用结构),子类实现特定的「步骤」(定制化需求)**。 ``` Java中的设计模式:模板模式 是把抽象类整体看成是一个模版,模板中不能决定的东西,抽象成方法 让使用模板的类(也就是抽象类的子类)去重写抽象方法,实现定制化的需求。 模板模式的优势: 模板已经定义了通用的结构,使用者只需要关心自己需要实现的功能就可以了 ``` ### 2. 模板类的组成 **文件**:`MyOneDayTemplate.java` 一般模板类分为两部分: | 组成部分 | 关键字 | 说明 | |---------|--------|------| | **模板方法** | `final` | 定义执行步骤的骨架,子类不能修改(不能重写) | | **抽象方法** | `abstract` | 需要被子类重写的方法,具体行为由子类决定 | ```java package course; public abstract class MyOneDayTemplate { /** * 模板方法——使用 final 修饰,只能使用,不能修改 */ public final void myOneDay() { System.out.print("《我的一天》"); System.out.print("今天我吃了"); this.eat(); // 步骤 1:吃(抽象方法,子类实现) System.out.print("然后我又"); this.sleep(); // 步骤 2:睡(抽象方法,子类实现) System.out.print("最后我又玩了"); this.play(); // 步骤 3:玩(抽象方法,子类实现) System.out.print("好开心哟~"); System.out.println(); } // 抽象方法——由子类提供具体的实现 public abstract void eat(); public abstract void sleep(); public abstract void play(); } ``` > **关键点**:`myOneDay()` 用 `final` 修饰,确保子类不能篡改这个方法的结构。子类只需关注 `eat()`、`sleep()`、`play()` 三个抽象方法的具体实现。 ### 3. 子类实现 **文件**:`ZhSanOneDay.java`、`LiSiOneDay.java` ```java // 张三的一天 public class ZhSanOneDay extends MyOneDayTemplate { @Override public void eat() { System.out.println("饭"); } @Override public void sleep() { System.out.println("菜"); } @Override public void play() { System.out.println("小狗"); } } // 李四的一天 public class LiSiOneDay extends MyOneDayTemplate { @Override public void eat() { System.out.println("饭"); } @Override public void sleep() { System.out.println("菜"); } @Override public void play() { System.out.println("小狗"); } } ``` ### 4. 测试类 **文件**:`Test03.java` ```java public class Test03 { public static void main(String[] args) { ZhSanOneDay zhSanOneDay = new ZhSanOneDay(); zhSanOneDay.myOneDay(); LiSiOneDay li = new LiSiOneDay(); li.myOneDay(); } } ``` **预期输出**: ``` 《我的一天》今天我吃了饭然后我又菜最后我又玩了好开心哟~ 《我的一天》今天我吃了饭然后我又菜最后我又玩了好开心哟~ ``` ### 5. 模板方法模式的优势 | 优势 | 说明 | |------|------| | **复用性** | 公共的流程代码写在父类的模板方法中,所有子类共用 | | **扩展性** | 新增一种「一天」只需新建子类,重写抽象方法即可 | | **规范性** | 父类通过模板方法强制规定了执行顺序和结构,子类无法改变 | | **关注分离** | 使用者只需关心自己的定制逻辑(吃什么/睡什么/玩什么),不用关注整体流程 | ### 6. 模板方法 vs 普通继承 + 方法重写 | 对比维度 | 普通继承 + 方法重写 | 模板方法模式 | |---------|-------------------|-------------| | **父类方法** | 普通方法,子类可选择重写或不重写 | 模板方法用 `final` 修饰,**禁止重写** | | **执行流程** | 由调用方决定调用顺序 | 父类模板方法定义固定执行顺序 | | **强制约束** | 弱——子类可以不重写 | 强——子类必须重写抽象方法 | | **设计意图** | 复用代码 | 定义算法骨架 + 强制子类实现特定步骤 | --- ## 六、随堂练习要点 **练习目标**:理解抽象类的定义和使用 1. **定义抽象父类**:使用 `public abstract class` 声明,定义抽象方法时不要写方法体 2. **实现具体子类**:使用 `extends` 继承抽象类,使用 `@Override` 重写所有抽象方法 3. **测试类验证**:创建子类对象,调用重写后的方法验证正确性 --- ## 七、面试常见问题 **Q1:抽象类能不能被实例化?** > 不能。抽象类不能直接 `new` 创建对象。 **Q2:抽象类中一定要有抽象方法吗?** > 不一定。抽象类中可以没有抽象方法,但有抽象方法的类必须是抽象类。 **Q3:抽象类的子类必须重写所有抽象方法吗?** > 不一定。子类有两个选择:① 重写父类所有抽象方法变为具体子类;② 子类也声明为抽象类,继承父类的抽象方法。 **Q4:抽象类可以有构造方法吗?** > 可以。虽然抽象类不能直接创建对象,但其构造方法可以在子类实例化时通过 `super()` 调用,用于初始化父类成员。 **Q5:`abstract` 和 `final` 能否同时修饰一个类或方法?** > 不能。`abstract` 表示需要被继承/重写,`final` 表示不能被继承/重写,二者语义冲突。 **Q6:`final` 修饰引用类型变量时,对象内容还能修改吗?** > 可以。`final` 修饰引用类型变量时,只是保证引用地址不变(不能重新指向其他对象),但该对象内部的成员变量仍然可以通过 setter 修改。 **Q7:`final` 修饰的类有哪些常见例子?** > `String` 类、`Math` 类、`Integer` 等包装类都是用 `final` 修饰的,它们不能被继承。 **Q8:什么是模板方法设计模式?它的核心思想是什么?** > 模板方法模式(Template Method Pattern)是抽象类的经典应用场景。核心思想是:**父类定义算法的「骨架」(通用结构),子类实现特定的「步骤」(定制化需求)**。父类中的模板方法用 `final` 修饰禁止重写,抽象方法用 `abstract` 修饰强制子类实现。 **Q9:模板方法模式中,模板方法为什么用 `final` 修饰?** > 为了防止子类篡改算法的执行流程。如果模板方法不被 `final` 修饰,子类可以重写它,破坏父类定义好的步骤结构,失去了「模板」的意义。 --- ## 八、拓展阅读 - [Java 抽象类(Oracle 官方文档)](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html) - 抽象类 vs 接口(Interface)的区别(将在后续课程中讲解) - 模板方法设计模式(Template Method Pattern)—— 抽象类的经典应用场景