Java中的接口(Interface)与抽象类(Abstract Class)

前言

接口与抽象,Java中绕不开的主题,但是又总感觉二者之间有些东西模模糊糊没有理清,今天来认真整理一下。

一、基本原理

接口

纯行为契约,定义方法的签名 (不包含实现)。Java 8+支持默认方法(default)和静态方法。

  • 实现多继承行为:类可同时实现多个接口
  • 强制统一规范 :不同类的相同行为标准化(如Comparable
  • 解耦:声明与实现分离(依赖倒置原则)

抽象类

可包含部分实现的模板,允许定义抽象方法(无实现)、具体方法、成员变量和构造器。

  • 代码复用:为继承体系提供公共实现(如模板方法模式)
  • 状态共享:可在基类中封装共享状态(字段)
  • 控制扩展:定义必须由子类实现的约束

核心差异

特性 接口 抽象类
实例字段 ❌(Java 17仅支持常量)
构造方法
方法实现 Java 8+支持默认/静态方法
多重继承 ✅(多个接口) ❌(单继承)
设计目的 "能做什么"(行为) "是什么"(层次结构)

二、普遍用法

1. 定义行为契约(接口)

java 复制代码
// 接口定义支付行为
interface PaymentProcessor {
    // 抽象方法
    void processPayment(double amount);

    // Java 8 默认方法(可选实现)
    default void validatePayment(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("无效金额");
    }
}

// 具体实现类
class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        validatePayment(amount); // 调用默认方法
        System.out.println("信用卡支付: " + amount);
    }
}

2. 复用公共逻辑(抽象类)

java 复制代码
// 抽象类提供模板方法
abstract class AbstractDatabaseClient {
    // 共享状态
    private final String connectionUrl;
    
    // 构造器注入
    public AbstractDatabaseClient(String url) {
        this.connectionUrl = url;
        initializeConnection();
    }

    // 具体方法(复用)
    private void initializeConnection() {
        System.out.println("初始化DB连接: " + connectionUrl);
    }
    
    // 抽象方法(子类必须实现)
    public abstract void executeQuery(String sql);
}

// 具体子类
class MySQLClient extends AbstractDatabaseClient {
    // 通过构造方法注入参数
    public MySQLClient(String url) { super(url); }
    
    @Override
    public void executeQuery(String sql) {
        System.out.println("执行MySQL查询: " + sql);
    }
}

⚠️ 版本差异说明

  • Java 8+ :接口支持default/static方法
  • Java 9+ :接口支持private方法(代码复用)
  • Java 17+sealed接口/抽象类控制继承
java 复制代码
// Java 17 sealed接口示例
public sealed interface PaymentProcessor 
    permits CreditCardProcessor, CryptoProcessor { ... }

三、设计原则

优先选择接口(符合ISP原则)

java 复制代码
// 细粒度接口设计(避免God Interface)
interface Loggable { void log(); }
interface Cacheable { void cache(); }

抽象类用于模板方法

java 复制代码
abstract class TemplateService {
    // 固定算法骨架
    public final void execute() {
        preProcess();
        doBusinessLogic(); // 抽象钩子
        postProcess();
    }
    protected abstract void doBusinessLogic();
}

✅ 总结决策树

flowchart TD A[需要多继承?] -- Yes --> B(选接口) A -- No --> C{需要共享状态或公共实现?} C -- Yes --> D(选抽象类) C -- No --> E{需要定义行为契约?} E -- Yes --> B E -- No --> F(可能不需要接口/抽象类)

四、避坑

⚠️ 坑1:接口默认方法冲突

问题:多个接口的同名默认方法引发冲突

java 复制代码
interface A { default void foo() {} }
interface B { default void foo() {} }
class C implements A, B {} // 编译错误!

解决:在实现类中强制重写:

java 复制代码
class C implements A, B {
    @Override 
    public void foo() {
        A.super.foo(); // 显式选择实现
    }
}

⚠️ 坑2:抽象类构造器陷阱

问题:子类忘记调用超类构造器导致状态未初始化

java 复制代码
abstract class AbstractResource {
    private String resourceId;
    protected AbstractResource(String id) { this.resourceId = id; }
}
class SubResource extends AbstractResource {
    // 未调用super(id) -> NullPointerException!
}

解决

  1. IDE配置@RequiredArgsConstructor(Lombok)
  2. 代码审查强制验证

⚠️ 坑3:扩展性破坏

问题:向接口添加新方法导致遗留实现类报错

规避

  • Java 8+:优先用default方法(向后兼容)
  • 发布新接口V2(如PaymentProcessorV2

⚠️ 坑4:状态泄漏(抽象类特有)

问题:子类错误修改抽象类共享状态:

java 复制代码
abstract class AbstractSession {
    protected List<String> sessions = new ArrayList<>();
}
class UserSession extends AbstractSession {
    public void clear() { sessions.clear(); } // 影响所有子类实例!
}

规避

  1. 字段设为private + 提供受保护访问器
  2. 文档明确状态所有权

结语

黄金法则

  1. 行为定义必选接口(特别是公共API)
  2. 纵向复用代码选抽象类(如领域模型基类)
  3. Java 17+优先使用sealed限制继承(安全控制)
相关推荐
绝无仅有2 小时前
Redis高级面试题解析:深入理解Redis的工作原理与优化策略
后端·面试·github
前行的小黑炭2 小时前
Android LayoutInflater 是什么?XML到View的过程
android·java·kotlin
绝无仅有2 小时前
Redis面试解答指南:了解Redis及其应用
后端·面试·github
尚久龙3 小时前
安卓学习 之 SeekBar(音视频播放进度条)
android·java·学习·手机·android studio
要一起看日出3 小时前
Shiro概述
java·spring boot·java-ee
不秃的开发媛3 小时前
Java开发入门指南:IDE选择与数据库连接详解
java·数据库·ide
没有bug.的程序员3 小时前
Redis Sentinel:高可用架构的守护者
java·redis·架构·sentinel
Zhen (Evan) Wang3 小时前
.NET 6 文件下载
java·前端·.net