接口和抽象类的区别是什么?怎么选择?

#Java#面试八股

接口和抽象类的区别是什么?怎么选择?

结论:抽象类是"is-a 的部分实现"——单继承、可以有构造器、实例字段、任意可见性的方法与状态,适合复用代码和定义模板方法;接口是"can-do 的能力契约"——多实现、字段隐式为 public static final、方法在 JDK 8 后可以有 default/static、JDK 9 后可以有 private 方法,适合跨继承体系声明能力。选择标准:要共享状态或骨架逻辑用抽象类,要定义可插拔的能力用接口;两者常常配合(接口 + 抽象骨架类)。

1. 逐项对比

维度抽象类接口
继承/实现类只能 extends 一个类可以 implements 多个
构造器有(供子类调用)无
实例字段任意类型、任意可见性只能是 public static final 常量
方法抽象、具体、private、protected 都可默认 public abstract;JDK 8 起 default/static;JDK 9 起 private
多态方式继承 + 重写实现 + 重写
设计意图代码复用、模板方法、受控扩展契约、解耦、能力组合
演化能力加抽象方法会破坏所有子类加 default 方法不破坏实现类
典型例子AbstractList、InputStreamList、Comparable、AutoCloseable

2. 结构差异示意

抽象类:          接口:
  Animal            Flyable     Swimmable
   ├─ name 字段      (无状态)     (无状态)
   ├─ 构造器           ↑             ↑
   ├─ eat() 具体实现    └──── Duck ───┘   ← 可同时实现多个
   └─ sound() 抽象
        ↑
      Dog(单继承)

接口不能有实例状态这一点,正是它支持多实现的根本原因:没有状态就没有"菱形继承时字段归谁"的问题。

3. 代码:接口打契约,抽象类收公共逻辑

public interface PayChannel {
    String code();                                  // 抽象方法

    default boolean support(String currency) {      // 默认实现,实现类可不写
        return "CNY".equals(currency);
    }

    static PayChannel of(String code) {            // 静态工厂:按编码找实现
        return ServiceLoader.load(PayChannel.class).stream()
                .map(ServiceLoader.Provider::get)
                .filter(p -> p.code().equals(code))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("unknown: " + code));
    }
}

public abstract class AbstractPayChannel implements PayChannel {
    protected final RetryTemplate retry;            // 共享状态

    protected AbstractPayChannel(RetryTemplate retry) {
        this.retry = retry;
    }

    protected abstract void doPay(long cents);      // 子类实现

    public final void pay(long cents) {             // 模板方法:固定流程
        retry.execute(() -> doPay(cents));
    }
}

public class AlipayChannel extends AbstractPayChannel {
    public AlipayChannel(RetryTemplate retry) { super(retry); }
    @Override public String code() { return "alipay"; }
    @Override protected void doPay(long cents) { /* 调用支付宝 SDK */ }
}

这里接口负责"能被支付框架识别",抽象类负责"重试 + 模板流程",职责清晰。pay() 用 final 锁住流程,子类只能填 doPay() 这一空——这就是模板方法模式(Template Method)。

4. 选择清单

需求选择
多个实现共享字段/构造逻辑抽象类
定义一组能力,允许一个类同时具备多个接口
未来可能加方法又不想破坏实现类接口 + default 方法
需要 protected 方法给子类用抽象类
需要被远程调用/序列化的契约接口
框架扩展点(SPI)接口
既要契约又要复用接口 + 抽象骨架类(如 AbstractList)

5. 常见追问

追问回答要点
接口为什么不能有构造器?接口不能被实例化,没有实例状态需要初始化
抽象类可以没有抽象方法吗?可以,此时只是"不能被实例化"的普通类
接口可以继承多个接口吗?可以,interface C extends A, B;类实现 C 时要处理冲突
抽象类可以实现接口吗?可以,且可以不实现接口方法,留给子类
default 方法冲突怎么解决?实现类必须重写,或用 A.super.method() 显式指定
接口里的字段为什么必须是常量?接口描述契约、不持有状态;public static final 是编译期常量,可安全共享
抽象类能 final 吗?不能,final 与 abstract 互斥

小结

抽象类解决"复用",接口解决"解约"。一个实用判断:如果子类之间需要共享状态或固定流程,用抽象类;如果只是想让不同家族的类都具备某种能力,用接口。现代 Java 更偏向"接口 + 组合",因为继承是强耦合,接口演化也更安全。

笔记加载中…