编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • 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);
    }
}

# 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);
    }
}

结果展示: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 反序列化"); }
}

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");  // 编译错误!
    }
}

运行结果:

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

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

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

===== 汇总 =====
学生成绩CSV:共处理 3 条有效记录
数据库配置:共处理 3 条有效记录

案例知识点映射:

知识点 案例中的体现
抽象类不能实例化 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"); }
}

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

# 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;
    }
}

# 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 + " 格式");
    }
}

# 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);  // 使用默认方法
    }
}

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

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

// 通过接口名调用
boolean result = StringUtils.isEmpty("");  // true

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

# 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);
    }
}

疑惑: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,再找方法偏移

结果展示: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();
        }
    }
}

运行结果:

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

--- 初始化插件 ---
  [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

案例知识点映射:

知识点 案例中的体现
接口定义 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() 的冲突?
}

思考: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 有格式化能力)");
    }
}

运行结果:

===== 抽象类 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 有格式化能力)

案例知识点映射:

对比项 抽象类(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/28, 17:55:19
继承和多态
异常处理

← 继承和多态 异常处理→

最近更新
01
科学方法实践论法
06-28
02
辩证思维矛盾论法
06-28
03
毛选中的调查观念
06-28
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式