编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
      • 函数方法
      • 类和对象
      • 继承和多态
      • 接口和抽象类
        • 9.1 抽象类
          • 9.1.1 抽象类概念
          • 9.1.2 抽象类语法
          • 9.1.3 抽象类特点
          • 9.1.4 综合案例:数据处理框架
          • 9.1.5 抽象类训练题
        • 9.2 接口
          • 9.2.1 接口概念
          • 9.2.2 接口语法
          • 9.2.3 接口的多实现
          • 9.2.4 接口默认方法(JDK8+)
          • 9.2.5 接口静态方法(JDK8+)
          • 9.2.6 接口私有方法(JDK9+)
          • 9.2.7 接口的演进历史
          • 9.2.8 接口方法调用的底层原理
          • 9.2.9 综合案例:插件系统
          • 9.2.10 接口训练题
        • 9.3 抽象类和接口区别
          • 9.3.1 核心区别
          • 9.3.2 选择建议
          • 9.3.3 综合案例:日志框架设计
          • 9.3.4 抽象类和接口区别训练题
      • 异常处理
      • 集合框架
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

接口和抽象类

# 09.接口和抽象类

# 目录介绍

  • 9.1 抽象类
    • 9.1.1 抽象类概念
    • 9.1.2 抽象类语法
    • 9.1.3 抽象类特点
    • 9.1.4 综合案例:数据处理框架
    • 9.1.5 抽象类训练题
  • 9.2 接口
    • 9.2.1 接口概念
    • 9.2.2 接口语法
    • 9.2.3 接口的多实现
    • 9.2.4 接口默认方法(JDK8+)
    • 9.2.5 接口静态方法(JDK8+)
    • 9.2.6 接口私有方法(JDK9+)
    • 9.2.7 接口的演进历史
    • 9.2.8 接口方法调用的底层原理
    • 9.2.9 综合案例:插件系统
    • 9.2.10 接口训练题
  • 9.3 抽象类和接口区别
    • 9.3.1 核心区别
    • 9.3.2 选择建议
    • 9.3.3 综合案例:日志框架设计
    • 9.3.4 抽象类和接口区别训练题

# 9.1 抽象类

# 9.1.1 抽象类概念

抽象类是不能被实例化的类,用 abstract 关键字修饰。抽象类可以包含抽象方法(没有方法体的方法),子类必须实现这些抽象方法。

抽象类的设计哲学:抽象类代表一种"不完整"的类定义。它提供了部分实现(共同的属性和方法),把不确定的部分(抽象方法)留给子类去完成。这就是模板方法模式(Template Method Pattern)的基础。

对比 C++:C++ 通过纯虚函数 virtual void func() = 0; 定义抽象类,Java 用 abstract 关键字更加明确。

# 9.1.2 抽象类语法

// 抽象类
public abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // 抽象方法:没有方法体,子类必须实现
    public abstract double area();
    public abstract double perimeter();

    // 普通方法:抽象类可以有具体实现的方法
    public void describe() {
        System.out.println("这是一个" + color + "的形状,面积为:" + area());
    }
}

// 子类:必须实现所有抽象方法
public class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}

public class Rectangle extends Shape {
    private double width, height;

    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }

    @Override
    public double perimeter() {
        return 2 * (width + height);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 9.1.3 抽象类特点

  1. 不能被实例化(new Shape() 编译错误)。
  2. 可以有构造方法(供子类调用)。
  3. 可以有成员变量。
  4. 可以有普通方法和抽象方法。
  5. 子类必须实现所有抽象方法,除非子类也是抽象类。

抽象类的底层原理:在字节码层面,抽象类使用 ACC_ABSTRACT 标志修饰。抽象方法只有方法签名而没有 Code 属性。JVM 在加载抽象类时不会为其分配虚方法表中的方法入口,而是等到子类实现后才填充。如果通过反射调用 newInstance() 尝试实例化抽象类,JVM 会抛出 InstantiationException。

疑惑:抽象类既然不能实例化,为什么还需要构造方法?

答疑:抽象类的构造方法不是给自己用的,而是给子类调用的。子类在构造时必须先调用父类构造方法(super(...)),完成父类字段的初始化。这正是 Java 对象构造的链式初始化机制。

论证:

abstract class Database {
    protected String url;
    protected int timeout;
    
    // 抽象类的构造方法:初始化共有字段
    public Database(String url, int timeout) {
        this.url = url;
        this.timeout = timeout;
        System.out.println("Database 构造完成");
    }
    
    public abstract void connect();
}

class MySQLDatabase extends Database {
    private String charset;
    
    public MySQLDatabase(String url, int timeout, String charset) {
        super(url, timeout);  // 必须调用父类构造方法
        this.charset = charset;
        System.out.println("MySQLDatabase 构造完成");
    }
    
    @Override
    public void connect() {
        System.out.println("连接 MySQL: " + url);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

结果展示:new MySQLDatabase("jdbc:mysql://localhost", 30, "UTF-8") 的输出顺序是 "Database 构造完成" → "MySQLDatabase 构造完成",证明抽象类的构造方法在子类之前执行。

模板方法模式(Template Method Pattern):

抽象类最经典的应用就是模板方法模式。父类定义算法骨架,子类实现具体步骤:

abstract class DataProcessor {
    // 模板方法(final 防止子类重写算法骨架)
    public final void process() {
        readData();
        parseData();
        validate();
        save();
    }
    
    // 各步骤由子类实现
    protected abstract void readData();
    protected abstract void parseData();
    
    // 钩子方法(hook):有默认实现,子类可选择重写
    protected void validate() {
        System.out.println("默认校验通过");
    }
    
    protected void save() {
        System.out.println("保存到默认存储");
    }
}

class CSVProcessor extends DataProcessor {
    @Override
    protected void readData() { System.out.println("读取 CSV 文件"); }
    
    @Override
    protected void parseData() { System.out.println("按逗号分割解析"); }
    
    @Override
    protected void validate() { System.out.println("CSV 格式校验"); }
}

class JSONProcessor extends DataProcessor {
    @Override
    protected void readData() { System.out.println("读取 JSON 文件"); }
    
    @Override
    protected void parseData() { System.out.println("JSON 反序列化"); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

JDK 中模板方法模式的典型应用:AbstractList.get()、InputStream.read(byte[])(调用抽象的 read() 单字节方法)。

# 9.1.4 综合案例:数据处理框架

本案例综合运用抽象类的核心知识:抽象方法、构造方法链、模板方法模式、钩子方法。

/**
 * 数据处理框架 —— 抽象类综合案例
 * 知识点:抽象类、抽象方法、构造方法、模板方法、钩子方法
 */
abstract class DataSource {
    protected String name;
    protected int recordCount;

    // 抽象类的构造方法(供子类 super 调用)
    public DataSource(String name) {
        this.name = name;
        this.recordCount = 0;
        System.out.println("[构造] DataSource(" + name + ")");
    }

    // 抽象方法:由子类决定如何读取和解析
    public abstract String[] readData();
    public abstract String parseRecord(String raw);

    // 钩子方法:有默认实现,子类可选择重写
    protected boolean validate(String record) {
        return record != null && !record.isEmpty();
    }

    // 模板方法(final 防止子类打乱流程)
    public final void process() {
        System.out.println("\n===== 处理 " + name + " =====");
        String[] rawData = readData();
        System.out.println("读取到 " + rawData.length + " 条原始数据");

        int valid = 0, invalid = 0;
        for (String raw : rawData) {
            String record = parseRecord(raw);
            if (validate(record)) {
                System.out.println("  ✓ " + record);
                valid++;
            } else {
                System.out.println("  ✗ 无效: " + raw);
                invalid++;
            }
        }
        recordCount = valid;
        System.out.printf("结果:有效 %d 条,无效 %d 条%n", valid, invalid);
    }

    // 普通方法
    public String getSummary() {
        return name + ":共处理 " + recordCount + " 条有效记录";
    }
}

// 子类1:CSV数据源
class CSVDataSource extends DataSource {
    private String[] csvLines;

    public CSVDataSource(String name, String[] csvLines) {
        super(name);  // 调用抽象类构造方法
        this.csvLines = csvLines;
        System.out.println("[构造] CSVDataSource 准备就绪");
    }

    @Override
    public String[] readData() {
        return csvLines;
    }

    @Override
    public String parseRecord(String raw) {
        // CSV 解析:取第一个逗号前后的内容
        String[] parts = raw.split(",");
        if (parts.length >= 2) {
            return parts[0].trim() + " -> " + parts[1].trim();
        }
        return null;
    }
    
    // 重写钩子方法:增加额外校验
    @Override
    protected boolean validate(String record) {
        return super.validate(record) && record.contains("->");
    }
}

// 子类2:键值对数据源
class KeyValueDataSource extends DataSource {
    private String[] kvLines;

    public KeyValueDataSource(String name, String[] kvLines) {
        super(name);
        this.kvLines = kvLines;
    }

    @Override
    public String[] readData() {
        return kvLines;
    }

    @Override
    public String parseRecord(String raw) {
        if (raw.contains("=")) {
            String[] parts = raw.split("=", 2);
            return "[" + parts[0].trim() + "] = " + parts[1].trim();
        }
        return null;
    }
    // 使用默认的 validate 钩子方法
}

public class DataProcessDemo {
    public static void main(String[] args) {
        // 1. 构造方法链演示
        String[] csv = {"张三,90", "李四,85", "无效数据", "王五,92"};
        DataSource csvSource = new CSVDataSource("学生成绩CSV", csv);
        
        String[] kv = {"host=localhost", "port=3306", "=空键", "db=mydb"};
        DataSource kvSource = new KeyValueDataSource("数据库配置", kv);

        // 2. 模板方法 + 多态
        DataSource[] sources = {csvSource, kvSource};
        for (DataSource source : sources) {
            source.process();  // 同一个 process(),不同的读取和解析行为
        }

        // 3. 普通方法调用
        System.out.println("\n===== 汇总 =====");
        for (DataSource source : sources) {
            System.out.println(source.getSummary());
        }

        // 4. 抽象类不能实例化
        // new DataSource("test");  // 编译错误!
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

运行结果:

[构造] DataSource(学生成绩CSV)
[构造] CSVDataSource 准备就绪
[构造] DataSource(数据库配置)

===== 处理 学生成绩CSV =====
读取到 4 条原始数据
  ✓ 张三 -> 90
  ✓ 李四 -> 85
  ✗ 无效: 无效数据
  ✓ 王五 -> 92
结果:有效 3 条,无效 1 条

===== 处理 数据库配置 =====
读取到 4 条原始数据
  ✓ [host] = localhost
  ✓ [port] = 3306
  ✗ 无效: =空键
  ✓ [db] = mydb
结果:有效 3 条,无效 1 条

===== 汇总 =====
学生成绩CSV:共处理 3 条有效记录
数据库配置:共处理 3 条有效记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

案例知识点映射:

知识点 案例中的体现
抽象类不能实例化 new DataSource("test") 编译错误
抽象方法 readData()、parseRecord() 由子类实现
构造方法链 CSVDataSource → super(name) → DataSource
模板方法模式 process() 定义流程,子类填充细节
钩子方法 validate() 有默认实现,CSV子类重写增强
多态 DataSource[] 数组统一调用 process()

# 9.1.5 抽象类训练题

训练1:创建一个抽象类 Employee,包含属性 name 和 baseSalary,以及抽象方法 double calculateSalary()。然后创建 FullTimeEmployee(全职,月薪制)和 PartTimeEmployee(兼职,按小时计薪)两个子类。

训练2:以下代码能编译通过吗?为什么?

abstract class A {
    abstract void method1();
    abstract void method2();
}
abstract class B extends A {
    void method1() { System.out.println("B.method1"); }
    // 没有实现 method2
}
class C extends B {
    void method2() { System.out.println("C.method2"); }
}
1
2
3
4
5
6
7
8
9
10
11

思考:抽象类可以没有任何抽象方法吗?如果可以,这样做有什么意义?

# 9.2 接口

# 9.2.1 接口概念

接口(Interface)定义了一组行为规范,类通过实现接口来承诺遵循这些规范。接口是 Java 实现"多继承"的方式。

接口的底层原理:在字节码层面,接口方法的调用使用 invokeinterface 指令(而非普通虚方法的 invokevirtual)。这是因为一个类可以实现多个接口,接口方法在虚方法表中的位置不固定,需要在运行时通过接口方法表(itable) 进行二次查找。JDK 8 引入的 default 方法在字节码中标记为 ACC_PUBLIC,由实现类的虚方法表继承;如果多个接口有同名 default 方法,编译器会要求实现类显式重写以消除歧义——这就是 Java 在语言层面避免"菱形继承"问题的方式。

# 9.2.2 接口语法

// 定义接口
public interface Payable {
    // 接口中的变量默认是 public static final(常量)
    double TAX_RATE = 0.06;

    // 接口中的方法默认是 public abstract
    void pay(double amount);
    double getBalance();
}

// 实现接口
public class Account implements Payable {
    private double balance;

    @Override
    public void pay(double amount) {
        balance -= amount;
        System.out.println("支付 " + amount);
    }

    @Override
    public double getBalance() {
        return balance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 9.2.3 接口的多实现

一个类可以实现多个接口:

public interface Printable {
    void print();
}

public interface Exportable {
    void export(String format);
}

// 一个类可以同时实现多个接口
public class Report implements Printable, Exportable {
    @Override
    public void print() {
        System.out.println("打印报表");
    }

    @Override
    public void export(String format) {
        System.out.println("导出为 " + format + " 格式");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 9.2.4 接口默认方法(JDK8+)

JDK 8 允许接口中定义有具体实现的 default 方法:

public interface Loggable {
    // 默认方法:有具体实现,实现类可以选择重写或直接使用
    default void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

public class Account implements Loggable {
    // 不需要重写 log 方法,直接继承默认实现
    public void deposit(double amount) {
        log("存款 " + amount);  // 使用默认方法
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 9.2.5 接口静态方法(JDK8+)

public interface StringUtils {
    static boolean isEmpty(String str) {
        return str == null || str.isEmpty();
    }
}

// 通过接口名调用
boolean result = StringUtils.isEmpty("");  // true
1
2
3
4
5
6
7
8

注意:接口静态方法不会被继承,只能通过接口名调用,不能通过实现类调用。这和类的静态方法行为不同。

# 9.2.6 接口私有方法(JDK9+)

JDK 9 允许接口中定义 private 方法,用于在多个 default 方法之间共享代码:

public interface Loggable {
    default void logInfo(String msg) {
        log("INFO", msg);
    }
    
    default void logError(String msg) {
        log("ERROR", msg);
    }
    
    // 私有方法:提取公共逻辑,不对外暴露
    private void log(String level, String msg) {
        System.out.printf("[%s] %s: %s%n",
            java.time.LocalTime.now(), level, msg);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

疑惑:JDK 8 才给接口加了 default 方法,JDK 9 为什么又加了 private 方法?

答疑:当多个 default 方法有重复逻辑时,无法在接口内部复用。JDK 8 中只能把公共逻辑放到另一个 default 方法中,但这样会暴露给实现类。private 方法解决了这个封装问题。

# 9.2.7 接口的演进历史

JDK 版本 接口新增能力 解决的问题
JDK 1.0 抽象方法 + 常量 定义行为契约
JDK 8 default 方法、static 方法 接口演化不破坏兼容性
JDK 9 private 方法 default 方法间代码复用
JDK 14 Record 类可实现接口 不可变数据载体

# 9.2.8 接口方法调用的底层原理

疑惑:接口方法调用为什么比普通虚方法慢?

答疑:普通虚方法调用使用 invokevirtual 指令,方法在虚方法表(vtable)中的偏移量是固定的,可以直接通过偏移量定位。但接口方法使用 invokeinterface 指令——由于一个类可以实现多个接口,同一个接口方法在不同类的虚方法表中位置不同,需要通过接口方法表(itable) 进行二次查找。

论证:

vtable(虚方法表):偏移量固定
  Dog.vtable: [0]=toString [1]=hashCode [2]=makeSound [3]=eat
  Cat.vtable: [0]=toString [1]=hashCode [2]=makeSound [3]=eat
  → invokevirtual 直接取 vtable[2] 即 makeSound

itable(接口方法表):需要二次查找
  Dog 实现了 Flyable, Swimmable
  Cat 只实现了 Swimmable
  → invokeinterface 需要先在 itable 列表中查找 Flyable,再找方法偏移
1
2
3
4
5
6
7
8
9

结果展示:JIT 编译器会通过内联缓存(Inline Cache) 优化这个过程。对于单态调用(monomorphic),JIT 会直接缓存目标方法地址,性能与 invokevirtual 几乎无差别。只有多态调用(megamorphic,>2种类型)时才会退化为完整的 itable 查找。

# 9.2.9 综合案例:插件系统

本案例综合运用接口的核心知识:接口定义、多实现、default 方法、static 方法、private 方法(JDK9+)、接口演进。

import java.util.ArrayList;
import java.util.List;

/**
 * 插件系统 —— 接口综合案例
 * 知识点:接口、多实现、default方法、static方法、接口常量
 */

// 接口1:定义插件基本能力
interface Plugin {
    // 接口常量(public static final)
    String VERSION = "1.0";

    // 抽象方法:插件必须实现
    String getName();
    void execute(String input);

    // default 方法:提供默认的初始化行为
    default void init() {
        log("初始化插件: " + getName());
    }

    // default 方法:提供默认的关闭行为
    default void shutdown() {
        log("关闭插件: " + getName());
    }

    // JDK 9 private 方法:多个 default 方法共享的日志逻辑
    // 如果使用 JDK 8,可以改为 default 方法
    private void log(String msg) {
        System.out.printf("  [Plugin %s] %s%n", VERSION, msg);
    }

    // static 方法:通过接口名调用的工具方法
    static Plugin findByName(List<Plugin> plugins, String name) {
        for (Plugin p : plugins) {
            if (p.getName().equals(name)) return p;
        }
        return null;
    }
}

// 接口2:定义可配置能力
interface Configurable {
    void configure(String key, String value);
    String getConfig(String key);

    default void loadDefaults() {
        System.out.println("  加载默认配置...");
    }
}

// 实现类1:文本转换插件(只实现 Plugin)
class UpperCasePlugin implements Plugin {
    @Override
    public String getName() { return "UpperCase"; }

    @Override
    public void execute(String input) {
        System.out.println("  转换结果: " + input.toUpperCase());
    }
    // 使用 default 的 init() 和 shutdown()
}

// 实现类2:加密插件(实现 Plugin + Configurable,多实现)
class CaesarPlugin implements Plugin, Configurable {
    private int shift = 3;  // 默认移位

    @Override
    public String getName() { return "Caesar"; }

    @Override
    public void execute(String input) {
        StringBuilder sb = new StringBuilder();
        for (char c : input.toCharArray()) {
            if (Character.isLetter(c)) {
                char base = Character.isUpperCase(c) ? 'A' : 'a';
                sb.append((char) ((c - base + shift) % 26 + base));
            } else {
                sb.append(c);
            }
        }
        System.out.println("  加密结果(移位" + shift + "): " + sb);
    }

    // 重写 default 方法
    @Override
    public void init() {
        System.out.println("  [Caesar] 自定义初始化,加载密钥...");
    }

    @Override
    public void configure(String key, String value) {
        if ("shift".equals(key)) {
            shift = Integer.parseInt(value);
            System.out.println("  [Caesar] 设置移位: " + shift);
        }
    }

    @Override
    public String getConfig(String key) {
        if ("shift".equals(key)) return String.valueOf(shift);
        return null;
    }
}

// 实现类3:统计插件(实现 Plugin + Configurable)
class WordCountPlugin implements Plugin, Configurable {
    private boolean ignoreCase = true;

    @Override
    public String getName() { return "WordCount"; }

    @Override
    public void execute(String input) {
        String text = ignoreCase ? input.toLowerCase() : input;
        String[] words = text.split("\\s+");
        System.out.println("  单词数: " + words.length + " (忽略大小写: " + ignoreCase + ")");
    }

    @Override
    public void configure(String key, String value) {
        if ("ignoreCase".equals(key)) {
            ignoreCase = Boolean.parseBoolean(value);
        }
    }

    @Override
    public String getConfig(String key) {
        if ("ignoreCase".equals(key)) return String.valueOf(ignoreCase);
        return null;
    }
}

public class PluginSystemDemo {
    public static void main(String[] args) {
        // 创建插件列表
        List<Plugin> plugins = new ArrayList<>();
        plugins.add(new UpperCasePlugin());
        plugins.add(new CaesarPlugin());
        plugins.add(new WordCountPlugin());

        String testInput = "Hello World Java";

        System.out.println("╔═══════════════════════════════╗");
        System.out.println("║       插件系统演示            ║");
        System.out.println("╚═══════════════════════════════╝");

        // 1. 初始化所有插件(default 方法 vs 重写)
        System.out.println("\n--- 初始化插件 ---");
        for (Plugin p : plugins) {
            p.init();  // UpperCase/WordCount 用 default,Caesar 用重写版
        }

        // 2. 配置(多实现:Configurable 接口)
        System.out.println("\n--- 配置插件 ---");
        for (Plugin p : plugins) {
            if (p instanceof Configurable) {
                Configurable cfg = (Configurable) p;
                cfg.loadDefaults();  // Configurable 的 default 方法
            }
        }
        // 具体配置
        CaesarPlugin caesar = (CaesarPlugin) Plugin.findByName(plugins, "Caesar");
        if (caesar != null) {
            caesar.configure("shift", "5");
        }

        // 3. 执行所有插件(多态)
        System.out.println("\n--- 执行插件(输入: \"" + testInput + "\")---");
        for (Plugin p : plugins) {
            System.out.println("[" + p.getName() + "]");
            p.execute(testInput);
        }

        // 4. 接口静态方法
        System.out.println("\n--- 接口静态方法查找 ---");
        Plugin found = Plugin.findByName(plugins, "WordCount");
        if (found != null) {
            System.out.println("找到插件: " + found.getName());
        }

        // 5. 关闭(default 方法)
        System.out.println("\n--- 关闭插件 ---");
        for (Plugin p : plugins) {
            p.shutdown();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

运行结果:

╔═══════════════════════════════╗
║       插件系统演示            ║
╚═══════════════════════════════╝

--- 初始化插件 ---
  [Plugin 1.0] 初始化插件: UpperCase
  [Caesar] 自定义初始化,加载密钥...
  [Plugin 1.0] 初始化插件: WordCount

--- 配置插件 ---
  加载默认配置...
  加载默认配置...
  [Caesar] 设置移位: 5

--- 执行插件(输入: "Hello World Java")---
[UpperCase]
  转换结果: HELLO WORLD JAVA
[Caesar]
  加密结果(移位5): Mjqqt Btwqi Ofaf
[WordCount]
  单词数: 3 (忽略大小写: true)

--- 接口静态方法查找 ---
找到插件: WordCount

--- 关闭插件 ---
  [Plugin 1.0] 关闭插件: UpperCase
  [Plugin 1.0] 关闭插件: Caesar
  [Plugin 1.0] 关闭插件: WordCount
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

案例知识点映射:

知识点 案例中的体现
接口定义 Plugin、Configurable 接口
接口常量 Plugin.VERSION = "1.0"
抽象方法 getName()、execute()、configure()
多实现 CaesarPlugin implements Plugin, Configurable
default 方法 init()、shutdown()、loadDefaults()
default 方法重写 Caesar 的 init() 覆盖默认实现
private 方法(JDK9+) Plugin.log() 被多个 default 方法共享
static 方法 Plugin.findByName() 通过接口名调用
多态 + instanceof for (Plugin p : plugins) + 类型检查

# 9.2.10 接口训练题

训练1:定义一个 Sortable<T> 接口,包含 void sort() 方法。实现一个 SortableList<T extends Comparable<T>> 类,在 sort() 中实现冒泡排序。

训练2:如果一个类同时实现了两个接口,且两个接口有相同签名的 default 方法,写代码演示如何解决冲突:

interface A { default void hello() { System.out.println("A"); } }
interface B { default void hello() { System.out.println("B"); } }
class C implements A, B {
    // 如何解决 hello() 的冲突?
}
1
2
3
4
5

思考:JDK 8 引入 default 方法的主要动机是什么?(提示:想想 Collection 接口中新增的 stream() 方法。如果没有 default 方法,所有实现 Collection 的类都要改代码。)

# 9.3 抽象类和接口区别

# 9.3.1 核心区别

对比项 抽象类 接口
关键字 abstract class interface
继承/实现 单继承(extends) 多实现(implements)
构造方法 有 没有
成员变量 可以有各种成员变量 只能有 public static final 常量
方法 可以有抽象和具体方法 JDK 8 前只能有抽象方法,JDK 8+ 可以有 default 和 static 方法
设计思想 "是什么"(is-a 关系) "能做什么"(has-a 能力)

# 9.3.2 选择建议

  • 如果多个类有共同的属性和行为 → 用抽象类(如 Animal 有 name、age)
  • 如果只是定义能力/行为规范 → 用接口(如 Flyable、Swimmable)
  • 需要多继承效果 → 用接口

# 9.3.3 综合案例:日志框架设计

本案例通过设计一个简单的日志框架,直观展示抽象类和接口的不同使用场景和配合方式。

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 日志框架设计 —— 抽象类与接口对比综合案例
 * 用接口定义"能力",用抽象类定义"共同属性和模板"
 */

// 接口:定义"可格式化"能力(行为规范)
interface Formattable {
    String format(String level, String message);
}

// 接口:定义"可过滤"能力
interface Filterable {
    boolean shouldLog(String level);
}

// 抽象类:日志器基类(有共同属性、构造方法、模板方法)
abstract class Logger implements Formattable, Filterable {
    protected String name;
    protected String minLevel;
    private static final String[] LEVELS = {"DEBUG", "INFO", "WARN", "ERROR"};

    // 抽象类构造方法
    public Logger(String name, String minLevel) {
        this.name = name;
        this.minLevel = minLevel;
    }

    // 抽象方法:具体输出由子类决定
    protected abstract void output(String formattedMsg);

    // 模板方法:定义日志流程
    public final void log(String level, String message) {
        if (!shouldLog(level)) return;          // 接口方法:过滤
        String formatted = format(level, message); // 接口方法:格式化
        output(formatted);                      // 抽象方法:输出
    }

    // Filterable 接口的默认实现
    @Override
    public boolean shouldLog(String level) {
        return levelIndex(level) >= levelIndex(minLevel);
    }

    private int levelIndex(String level) {
        for (int i = 0; i < LEVELS.length; i++) {
            if (LEVELS[i].equalsIgnoreCase(level)) return i;
        }
        return 0;
    }

    // 快捷方法
    public void debug(String msg) { log("DEBUG", msg); }
    public void info(String msg)  { log("INFO", msg);  }
    public void warn(String msg)  { log("WARN", msg);  }
    public void error(String msg) { log("ERROR", msg); }
}

// 具体类1:控制台日志器
class ConsoleLogger extends Logger {
    public ConsoleLogger(String name, String minLevel) {
        super(name, minLevel);
    }

    @Override
    protected void output(String formattedMsg) {
        System.out.println(formattedMsg);
    }

    @Override
    public String format(String level, String message) {
        String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
        return String.format("[%s] [%-5s] [%s] %s", time, level, name, message);
    }
}

// 具体类2:简洁日志器(不同的格式化方式)
class SimpleLogger extends Logger {
    public SimpleLogger(String name, String minLevel) {
        super(name, minLevel);
    }

    @Override
    protected void output(String formattedMsg) {
        System.out.println(formattedMsg);
    }

    @Override
    public String format(String level, String message) {
        return level.charAt(0) + "/" + name + ": " + message;
    }
}

public class LogFrameworkDemo {
    public static void main(String[] args) {
        System.out.println("===== 抽象类 vs 接口的使用对比 =====\n");

        // 1. is-a 关系:ConsoleLogger/SimpleLogger "是" Logger
        Logger console = new ConsoleLogger("App", "DEBUG");
        Logger simple = new SimpleLogger("DB", "WARN");

        System.out.println("--- ConsoleLogger (minLevel=DEBUG) ---");
        console.debug("调试信息");
        console.info("启动成功");
        console.warn("内存使用率较高");
        console.error("连接超时");

        System.out.println("\n--- SimpleLogger (minLevel=WARN) ---");
        simple.debug("这条不会打印");
        simple.info("这条也不会打印");
        simple.warn("慢查询警告");
        simple.error("数据库连接失败");

        // 2. 接口引用:has-a 能力
        System.out.println("\n--- 通过接口引用使用 ---");
        Formattable formatter = console;
        System.out.println("格式化测试: " + formatter.format("INFO", "测试消息"));

        Filterable filter = simple;
        System.out.println("DEBUG可记录? " + filter.shouldLog("DEBUG"));
        System.out.println("ERROR可记录? " + filter.shouldLog("ERROR"));

        // 3. 多态数组
        System.out.println("\n--- 多态统一处理 ---");
        Logger[] loggers = {console, simple};
        for (Logger logger : loggers) {
            logger.error("系统异常");  // 相同调用,不同格式输出
        }

        // 4. 设计对比总结
        System.out.println("\n===== 设计选择对比 =====");
        System.out.println("抽象类 Logger:");
        System.out.println("  ✓ 有共同属性(name, minLevel)");
        System.out.println("  ✓ 有构造方法初始化状态");
        System.out.println("  ✓ 模板方法定义流程");
        System.out.println("  ✓ is-a 关系(ConsoleLogger 是 Logger)");
        System.out.println("接口 Formattable/Filterable:");
        System.out.println("  ✓ 只定义行为规范,无状态");
        System.out.println("  ✓ 可以被任意类实现(多实现)");
        System.out.println("  ✓ has-a 能力(Logger 有格式化能力)");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

运行结果:

===== 抽象类 vs 接口的使用对比 =====

--- ConsoleLogger (minLevel=DEBUG) ---
[14:30:00] [DEBUG] [App] 调试信息
[14:30:00] [INFO ] [App] 启动成功
[14:30:00] [WARN ] [App] 内存使用率较高
[14:30:00] [ERROR] [App] 连接超时

--- SimpleLogger (minLevel=WARN) ---
W/DB: 慢查询警告
E/DB: 数据库连接失败

--- 通过接口引用使用 ---
格式化测试: [14:30:00] [INFO ] [App] 测试消息
DEBUG可记录? false
ERROR可记录? true

--- 多态统一处理 ---
[14:30:00] [ERROR] [App] 系统异常
E/DB: 系统异常

===== 设计选择对比 =====
抽象类 Logger:
  ✓ 有共同属性(name, minLevel)
  ✓ 有构造方法初始化状态
  ✓ 模板方法定义流程
  ✓ is-a 关系(ConsoleLogger 是 Logger)
接口 Formattable/Filterable:
  ✓ 只定义行为规范,无状态
  ✓ 可以被任意类实现(多实现)
  ✓ has-a 能力(Logger 有格式化能力)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

案例知识点映射:

对比项 抽象类(Logger) 接口(Formattable/Filterable)
用途 定义共同属性和行为模板 定义行为规范(能力)
有状态 ✓ name, minLevel ✗ 只有常量
构造方法 ✓ 初始化共有字段 ✗ 没有
继承方式 单继承 extends 多实现 implements
引用方式 Logger log = new ConsoleLogger(...) Formattable f = console
设计思想 "是什么"(is-a) "能做什么"(has-a)

# 9.3.4 抽象类和接口区别训练题

训练1:以下需求应该用抽象类还是接口?说明理由:

  • (a)定义"可序列化"的能力
  • (b)定义所有"交通工具"的共同属性和行为
  • (c)定义"可比较"的能力
  • (d)定义所有"银行账户"的共同属性和模板流程

训练2:JDK 8 之后接口可以有 default 方法和 static 方法。请列出接口和抽象类现在还剩的关键区别(至少3条)。

思考:在设计模式中,"面向接口编程"是一个核心原则。为什么 List<String> list = new ArrayList<>() 比 ArrayList<String> list = new ArrayList<>() 更好?

上次更新: 2026/06/10, 11:13:41
继承和多态
异常处理

← 继承和多态 异常处理→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式