# 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` 修饰,**禁止重写** | | **执行流程** | 由调用方决定调用顺序 | 父类模板方法定义固定执行顺序 | | **强制约束** | 弱——子类可以不重写 | 强——子类必须重写抽象方法 | | **设计意图** | 复用代码 | 定义算法骨架 + 强制子类实现特定步骤 | --- ## 六、代码块(Code Block) ### 1. 代码块的概念 **文件**:`Demo04.java` 在 Java 中,使用 `{}` 括起来的代码称为 **代码块**。 ```java { // 代码.... } ``` Java 中分为三种代码块: | 代码块类型 | 位置 | 执行时机 | |-----------|------|---------| | **局部代码块** | 方法或构造方法内部 | 调用到该方法时执行 | | **构造代码块** | 方法外部、类内部 | 每次创建对象时执行(在构造方法之前) | | **静态代码块** | 类内部,使用 `static` 修饰 | 类加载时执行(仅一次) | ### 2. 三种代码块示例 **文件**:`Demo04.java` ```java package course; public class Demo04 { public Demo04() { // 局部代码块——存在于方法或构造方法里面 { System.out.println("我是局部代码块,我存在于方法或构造方法里面"); } System.out.println("我是构造方法"); } // 构造代码块——在方法外面类里面 { System.out.println("我是构造代码块....我是在构造方法前面执行...."); } // 静态代码块——使用 static 修饰 static { System.out.println("我是静态代码块....我最先执行"); } public static void main(String[] args) { Demo04 d = new Demo04(); } } ``` ### 3. 执行顺序验证 执行 `main` 方法中的 `new Demo04()`,输出结果: ``` 我是静态代码块....我最先执行 我是构造代码块....我是在构造方法前面执行.... 我是局部代码块,我存在于方法或构造方法里面 我是构造方法 ``` **执行流程说明**: | 步骤 | 执行的代码 | 说明 | |------|----------|------| | ① | `static { ... }` | 类加载时执行静态代码块(仅执行一次) | | ② | `{ ... }`(构造代码块) | 每次创建对象时,在构造方法之前执行 | | ③ | `{ ... }`(局部代码块) | 构造方法内部按顺序执行到局部代码块 | | ④ | `构造方法体` | 构造方法的其他代码最后执行 | > **注意**:如果 `main` 方法中只声明变量 `Demo04 d;` 而不创建对象,则只有静态代码块执行,构造代码块和构造方法都不会执行。 ### 4. 三种代码块的特点总结 | 代码块类型 | 执行次数 | 执行时机 | 典型用途 | |-----------|---------|---------|---------| | **静态代码块** | 1 次(类加载时) | 最早执行 | 加载配置文件、初始化静态资源 | | **构造代码块** | 每次创建对象 | 在构造方法之前 | 提取多个构造方法的共同代码 | | **局部代码块** | 调用到该位置时 | 方法执行过程中 | 限制局部变量的作用域 | ### 5. 构造代码块的作用 构造代码块的一个重要特性:**把里面的代码在运行的时候,整合到被调用的构造方法中**。这意味着: - 如果一个类有多个重载的构造方法,每个构造方法中都需要执行的相同代码,可以提取到构造代码块中 - 构造代码块在 **所有构造方法之前** 执行 - 避免多个构造方法中的代码重复 ```java public class Example { // 构造代码块——所有构造方法都会执行 { System.out.println("初始化公共资源..."); } public Example() { System.out.println("无参构造"); } public Example(String name) { System.out.println("有参构造:" + name); } } ``` ## 七、接口(Interface) ### 1. 接口的引出——解决单继承的局限性 **文件**:`Demo05.java` Java 中的继承是 **单继承**,一个子类只能直接继承一个父类。这种方式逻辑清晰,但扩展性较低。 ``` 学生类 extends 人类 鹦鹉 extends 鸟类 鸟人 extends ? ← 既有人类的特征,又有鸟类的飞行能力,单继承无法解决 ``` 为了解决单继承带来的局限性,Java 提出了 **接口** 的概念。 **接口(Interface)** 是一种公共规范标准,只要符合这个规范标准,大家都可以通用。 ``` 鸟人 extends 人类 实现 飞行接口 ← 继承父类 + 实现接口,两者互补 ``` **接口存在的意义**: - **定义规范** — 规定实现类必须遵循的行为标准 - **功能拓展** — 在继承父类的基础上,额外扩展其他能力 > 俗话说的好:「**单继承 + 多实现**」——一个类只能继承一个父类,但可以同时实现多个接口。 ### 2. 接口的定义 **文件**:`Demo05.java`、`MyInterface.java` 接口的关键字是 `interface`。接口也是一种引用数据类型(或者说接口也是一种类)。 ```java public interface 接口名 { // 成员 } ``` **接口的成员特点**: | 成员类型 | 默认修饰符 | 说明 | |---------|-----------|------| | **变量** | `public static final` | 接口中定义的变量都是 **公开的静态常量** | | **方法** | `public abstract` | 接口中定义的方法都是 **公开的抽象方法** | ```java public interface MyInterface { // 变量——默认使用 public static final public static final String name = "张三"; // 方法——默认使用 public abstract public abstract void method(); } ``` > **注意**:接口和抽象类一样 **不能实例化**(不能创建对象),且接口 **没有构造方法**。 ### 3. 接口的实现 **文件**:`Demo05.java` 子类继承接口称为「**实现** 接口」,使用关键字 `implements`。 ```java public class 实现类 implements 接口 { // 必须重写(实现)接口中所有的抽象方法 } // 类可以同时继承父类 + 实现接口 public class 子类 extends 父类 implements 接口1, 接口2, ... { // 必须重写所有接口中的抽象方法 } ``` **接口的子类(实现类)规则**: - 要么重写(实现)接口中 **所有** 的抽象方法 - 要么子类也是一个 **抽象类** **接口与抽象类的对比**: | 对比维度 | 抽象类 | 接口 | |---------|-------|------| | **关键字** | `abstract class` | `interface` | | **构造方法** | 可以有 | 不能有 | | **成员变量** | 可以是任意类型 | 只能是 `public static final` 常量 | | **成员方法** | 可以有抽象/非抽象方法 | 只能是 `public abstract` 抽象方法 | | **子类方式** | `extends`(继承) | `implements`(实现) | | **单/多** | 单继承 | 可同时实现多个接口 | ### 4. USB 接口标准示例 #### 4.1 定义 USB 接口标准 **文件**:`Usb.java` ```java package course; public interface Usb { void startUsb(); // 默认 public abstract void useUsb(); // 默认 public abstract void stopUsb(); // 默认 public abstract } ``` USB 接口定义了一个规范标准:任何 USB 设备都必须具备「启动 → 使用 → 关闭」三个行为。 #### 4.2 父类 Mouse **文件**:`Mouse.java` ```java public class Mouse { private String brand; private double price; public Mouse(String brand, double price) { this.brand = brand; this.price = price; } // getter/setter 省略... public void showInfo() { System.out.println("品牌:" + brand + ",价格:" + price); } } ``` #### 4.3 实现类 LogiMouse(罗技鼠标) **文件**:`LogiMouse.java` ```java // 继承父类 Mouse + 实现 Usb 接口 public class LogiMouse extends Mouse implements Usb { public LogiMouse(String brand, double price) { super(brand, price); } @Override public void startUsb() { System.out.println("罗技鼠标启动"); } @Override public void useUsb() { System.out.println("罗技鼠标使用"); } @Override public void stopUsb() { System.out.println("罗技鼠标关闭"); } } ``` #### 4.4 实现类 ROGMouse(ROG 鼠标) **文件**:`ROGMouse.java` ```java public class ROGMouse extends Mouse implements Usb { public ROGMouse(String brand, double price) { super(brand, price); } @Override public void startUsb() { System.out.println("ROG鼠标启动"); } @Override public void useUsb() { System.out.println("ROG鼠标使用"); } @Override public void stopUsb() { System.out.println("ROG鼠标关闭"); } } ``` #### 4.5 测试类 Test04 **文件**:`Test04.java` ```java public class Test04 { public static void main(String[] args) { LogiMouse lm = new LogiMouse("罗技", 13.6); lm.showInfo(); // 调用父类 Mouse 的方法 lm.startUsb(); // 实现 Usb 接口的抽象方法 lm.useUsb(); // 实现 Usb 接口的抽象方法 lm.stopUsb(); // 实现 Usb 接口的抽象方法 } } ``` **预期输出**: ``` 品牌:罗技,价格:13.6 罗技鼠标启动 罗技鼠标使用 罗技鼠标关闭 ``` ### 5. 继承与接口的实现关系图 ``` ┌──────────┐ ┌──────────┐ │ Mouse │ │ Usb │ │ (父类) │ │ (接口) │ └────┬─────┘ └────┬─────┘ │ extends │ implements ┌────────┴──────────┐ │ │ │ │ ┌───┴────────┐ ┌──────┴─────┐ │ │ LogiMouse │ │ ROGMouse ├────┘ │ extends │ │ extends │ │ Mouse │ │ Mouse │ │ implements │ │ implements│ │ Usb │ │ Usb │ └────────────┘ └────────────┘ ``` ### 6. 接口的补充特性 **文件**:`Demo05.java` - **类与接口**:一个类虽然只能继承一个父类,但是可以同时 **实现多个接口** - **接口与接口**:接口之间可以 **多继承**(一个子接口可以继承多个父接口) ```java // 类实现多个接口 public class MyClass implements InterfaceA, InterfaceB, InterfaceC { // 必须重写所有接口中的抽象方法 } // 接口继承多个接口 public interface SubInterface extends InterfaceA, InterfaceB { // 子接口继承多个父接口的方法 } ``` --- ## 八、随堂练习要点 **练习目标**:理解抽象类、接口、代码块的基本使用 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` 修饰,子类可以重写它,破坏父类定义好的步骤结构,失去了「模板」的意义。 **Q10:Java 中的代码块有哪几种?它们的执行顺序是什么?** > 有三种:① 静态代码块(`static {}`)② 构造代码块(`{}`)③ 局部代码块(方法内的 `{}`)。执行顺序:静态代码块(类加载时执行一次)→ 构造代码块(每次创建对象时执行,在构造方法之前)→ 构造方法(含内部的局部代码块)。 **Q11:构造代码块有什么作用?** > 构造代码块可以把多个构造方法中共同的代码提取出来,避免重复。每次创建对象时,构造代码块会在构造方法之前自动执行,无论调用的是哪个重载的构造方法。 **Q12:接口和抽象类有什么区别?** > ① 关键字不同:抽象类是 `abstract class`,接口是 `interface`;② 构造方法:抽象类可以有,接口不能有;③ 成员变量:抽象类可以是任意类型,接口只能是 `public static final` 常量;④ 成员方法:抽象类可以有抽象/非抽象方法,接口只能是 `public abstract` 抽象方法;⑤ 子类方式:抽象类用 `extends` 继承,接口用 `implements` 实现;⑥ 单/多:抽象类是单继承,接口可以同时实现多个。 **Q13:一个类可以同时继承一个父类并实现多个接口吗?** > 可以。Java 中的类虽然只能单继承(一个父类),但可以同时实现多个接口。格式:`public class 子类 extends 父类 implements 接口1, 接口2 { ... }`。 **Q14:接口中的变量默认使用什么修饰符?方法呢?** > 接口中的变量默认使用 `public static final` 修饰(公开的静态常量),方法默认使用 `public abstract` 修饰(公开的抽象方法)。 **Q15:接口为什么不能实例化?接口有构造方法吗?** > 接口没有构造方法,因此不能实例化(不能创建对象)。接口是一种规范标准,需要通过实现类来提供具体的功能实现。 --- ## 十、拓展阅读 - [Java 抽象类(Oracle 官方文档)](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html) - [Java 接口(Oracle 官方文档)](https://docs.oracle.com/javase/tutorial/java/IandI/createinterface.html) - [Java 代码块初始化(Oracle 官方文档)](https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) - 抽象类 vs 接口(Interface)的区别对比 - 模板方法设计模式(Template Method Pattern)—— 抽象类的经典应用场景