前言
接口与抽象,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!
}
解决:
- IDE配置
@RequiredArgsConstructor
(Lombok) - 代码审查强制验证
⚠️ 坑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(); } // 影响所有子类实例!
}
规避:
- 字段设为
private
+ 提供受保护访问器 - 文档明确状态所有权
结语
黄金法则:
- 行为定义必选接口(特别是公共API)
- 纵向复用代码选抽象类(如领域模型基类)
- Java 17+优先使用
sealed
限制继承(安全控制)