前言
接口与抽象,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
}
class UserSession extends AbstractSession {
public void clear() { sessions.clear(); } // 影响所有子类实例!
}
规避:
字段设为private + 提供受保护访问器
文档明确状态所有权
结语
黄金法则:
行为定义必选接口(特别是公共API)
纵向复用代码选抽象类(如领域模型基类)
Java 17+优先使用sealed限制继承(安全控制)