# 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 { // 子接口继承多个父接口的方法 } ``` --- ## 八、接口的 JDK 版本演进(Default / Static / Private 方法) ### 1. 不同版本 JDK 的接口组成 **文件**:`Demo06.java` 传统接口中的方法都是 `public abstract` 抽象方法(没有方法体)。从 JDK8 开始,接口的组成逐渐丰富: | JDK 版本 | 新增特性 | 说明 | |---------|---------|------| | **JDK1.0~7** | 抽象方法 + 常量 | 方法只能是 `public abstract`,变量只能是 `public static final` | | **JDK 8** | **默认方法**(default) | 带有方法体,实现类不强制重写(可选重写,重写时去掉 `default`) | | **JDK 8** | **静态方法**(static) | 带有方法体,通过 **接口名** 直接调用 | | **JDK 9** | **私有方法**(private) | 带有方法体,用于抽取多个默认方法/静态方法中的公共代码 | ```java // 不同版本 JDK 接口的组成 public interface 接口名 { // JDK1.0~7:抽象方法 + 常量 public static final String NAME = "常量"; public abstract void abstractMethod(); // JDK8:默认方法(带方法体,不强制重写) public default void defaultMethod() { // 方法体... } // JDK8:静态方法(带方法体,通过接口名调用) public static void staticMethod() { // 方法体... } // JDK9:私有方法(带方法体,抽取公共代码) private void privateMethod() { // 方法体... } } ``` ### 2. 默认方法(default method)——JDK 8 **文件**:`MyDefaultInterface.java` ```java public interface MyDefaultInterface { // 默认方法——带有方法体,不强制实现类重写 default void method() { this.privateMethod(); System.out.println("我是接口的默认方法......"); } default void method02() { this.privateMethod(); System.out.println("我是接口的默认方法......"); } // 抽象方法——实现类必须重写 void add(int a, int b); } ``` **默认方法的特点**: | 特性 | 说明 | |------|------| | 关键字 | `default`(**不能省略**,`public` 可以省略) | | 方法体 | 可以有方法体(不再是纯抽象) | | 是否强制重写 | **不强制**,实现类可以选择重写或不重写 | | 重写时的注意点 | 重写时 **去掉 `default` 关键字** | | 多接口冲突 | 多个接口中存在相同签名的默认方法时,实现类 **必须重写** 该方法 | ### 3. 私有方法(private method)——JDK 9 **文件**:`MyDefaultInterface.java`、`Demo06.java` **引入原因**:JDK8 允许接口定义带方法体的默认方法和静态方法后,可能出现多个默认方法/静态方法中包含 **相同代码** 的情况。需要把这段代码抽取成共性方法,但又不想被外部使用,所以 JDK9 增加了私有方法。 ```java public interface MyDefaultInterface { // 多个默认方法调用私有方法,避免代码重复 default void method() { this.privateMethod(); // 调用私有方法 System.out.println("我是接口的默认方法......"); } default void method02() { this.privateMethod(); // 调用私有方法 System.out.println("我是接口的默认方法......"); } // JDK9 私有方法——抽取多个默认方法的公共代码 private void privateMethod() { System.out.println("我是属于MyDefaultInterface的我又n行代码..."); } } ``` **注意事项**: - 接口的 **默认方法** 可以调用接口的 **私有方法** - 接口的 **静态方法** 只能调用接口的 **静态私有方法** ### 4. 静态方法(static method)——JDK 8 **文件**:`MyDefaultInterface.java` ```java public interface MyDefaultInterface { // 静态方法——直接通过接口名调用 static void staticMethod() { System.out.println("我是接口的静态方法"); } } ``` 调用方式:`接口名.静态方法名()`,例如: ```java MyDefaultInterface.staticMethod(); ``` ### 5. 实现类与测试 **文件**:`MyDefaultInterfaceImpl.java`、`Test05.java` ```java // 实现类——只需要重写抽象方法,默认方法可以不用重写 public class MyDefaultInterfaceImpl implements MyDefaultInterface { @Override public void add(int a, int b) { System.out.println(a + b); } // method() 和 method02() 是默认方法,不强制重写 } ``` ```java public class Test05 { public static void main(String[] args) { MyDefaultInterfaceImpl mdii = new MyDefaultInterfaceImpl(); mdii.add(1, 2); // 调用实现类重写后的抽象方法 → 输出:3 mdii.method(); // 调用接口的默认方法 → 输出:我是属于... + 我是接口的默认方法...... MyDefaultInterface.staticMethod(); // 通过接口名直接调用静态方法 → 输出:我是接口的静态方法 } } ``` **预期输出**: ``` 3 我是属于MyDefaultInterface的我又n行代码... 我是接口的默认方法...... 我是接口的静态方法 ``` ### 6. 接口成员完整总结 | 成员类型 | 修饰符 | JDK 版本 | 是否有方法体 | 实现类是否必须重写 | 调用方式 | |---------|--------|---------|------------|-----------------|---------| | **常量** | `public static final` | 所有版本 | — | — | `接口名.常量名` | | **抽象方法** | `public abstract` | 所有版本 | ❌ 无 | ✅ 必须 | 对象调用 | | **默认方法** | `default` | JDK 8+ | ✅ 有 | ❌ 可选(冲突时强制) | 对象调用 | | **静态方法** | `static` | JDK 8+ | ✅ 有 | — | `接口名.方法名()` | | **私有方法** | `private` | JDK 9+ | ✅ 有 | — | 接口内部调用 | --- ## 九、随堂练习要点 **练习目标**:理解抽象类、接口、代码块的基本使用 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:接口为什么不能实例化?接口有构造方法吗?** > 接口没有构造方法,因此不能实例化(不能创建对象)。接口是一种规范标准,需要通过实现类来提供具体的功能实现。 **Q16:JDK 8 在接口中新增了哪些特性?** > JDK 8 新增了 **默认方法(default)** 和 **静态方法(static)**,它们都可以带有方法体。默认方法通过对象调用,静态方法通过接口名直接调用。 **Q17:接口的默认方法和抽象方法有什么区别?** > ① 默认方法有方法体,抽象方法没有;② 默认方法用 `default` 修饰,抽象方法用 `abstract` 修饰;③ 默认方法不强制实现类重写,抽象方法必须重写;④ 重写默认方法时要去掉 `default` 关键字。 **Q18:JDK 9 为什么要在接口中增加私有方法?** > JDK 8 允许接口中有带方法体的默认方法和静态方法后,可能出现多个默认方法或静态方法包含相同代码的情况。JDK 9 的私有方法就是为了抽取这些公共代码,避免重复,同时又不对外暴露。 --- ## 十一、拓展阅读 - [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/IandI/defaultMethods.html) - [Java 代码块初始化(Oracle 官方文档)](https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) - 抽象类 vs 接口(Interface)的区别对比 - 模板方法设计模式(Template Method Pattern)—— 抽象类的经典应用场景