Skip to content

02.2 — 抽象类与接口

定位: Java 中"抽象"的两种工具 — 模板 vs 契约,在不同场景下服务于不同的设计目的 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 设计动机差异、JDK8+ 接口变化、如何选择

一、这是什么?为什么需要它?

抽象类:模板方法模式

java
abstract class HttpServlet {
    // 模板方法 — 定义请求处理流程
    public void service(Request req, Response res) {
        if (req.getMethod().equals("GET")) {
            doGet(req, res);  // 子类实现
        } else if (req.getMethod().equals("POST")) {
            doPost(req, res); // 子类实现
        }
    }

    protected abstract void doGet(Request req, Response res);
    protected abstract void doPost(Request req, Response res);
}

为什么需要抽象类? — 复用骨架,子类只需填充差异部分。

没有抽象类会怎样?
  每个 Servlet 都要自己实现 service() 方法
  → 每个类都重复写 GET/POST 判断逻辑
  → 如果请求处理流程变化(加 PUT),所有类都要改

抽象类的价值:
  定义算法的骨架,子类只提供"变体"
  符合"模板方法模式" — 好莱坞原则:不要打电话给我们,我们会打给你

接口:契约设计

java
// 接口 = 能力契约
interface Flyable {
    void fly();  // 说"能飞"就行,不管怎么飞
}

// 不同的实现
class Bird implements Flyable {
    public void fly() { System.out.println("拍翅膀"); }
}
class Airplane implements Flyable {
    public void fly() { System.out.println("喷气引擎"); }
}

为什么需要接口? — 跨继承树的共性抽象。

Bird extends Animal
Airplane extends Machine

Bird 和 Airplane 没有继承关系
但都有 fly() 能力 → 用接口跨越继承树

二、原理拆解

2.1 核心差异对比

维度抽象类接口 (JDK8+)
核心语义"是什么" (is-a)"能做什么" (can-do)
构造方法可以有不能有
状态字段可以有 (任意)不能有 (仅 public static final)
方法实现可抽象可具体default 方法、static 方法
多重继承单继承多实现
访问修饰符任意public (JDK9 有 private 方法)
演进兼容性加抽象方法→所有子类需改default 方法→实现类不感知

2.2 JDK8+ 接口进化

为什么 JDK8 引入了 default 方法?

问题:Collection 接口想加 stream() 方法
如果加 abstract stream() → 所有实现类都要改!

解决方案:default 方法
interface Collection<E> {
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
}
→ 已有实现类自动继承 default 方法,无需修改

default 方法的多重继承冲突解决:

java
interface A {
    default void foo() { System.out.println("A"); }
}
interface B {
    default void foo() { System.out.println("B"); }
}

class C implements A, B {
    // 必须重写!否则编译错误
    @Override
    public void foo() {
        A.super.foo();  // 指定用 A 的实现
    }
}

规则:类优先于接口

  1. 如果子类有具体实现 → 用子类的
  2. 如果子类没有 → 找父类中最具体的 default 方法
  3. 冲突时 → 必须手动解决

2.3 选择决策树

是否需要共享状态(字段)?
├── 是 → 抽象类
└── 否 → 核心是"是什么"还是"能做什么"?
         ├── is-a 关系 → 抽象类
         └── can-do 能力 → 接口

需要多重继承?
├── 是 → 接口(必须)
└── 否 → 看是否需要构造函数/状态
         ├── 需要 → 抽象类
         └── 不需要 → 接口(更灵活)

三、图解全景

┌─────────────────────────────────────────────────────────────┐
│             抽象类 vs 接口 内存结构对比                        │
│                                                              │
│  抽象类:                                                     │
│  abstract class Shape {                                      │
│      int x, y;                    ← 状态字段                  │
│      abstract void draw();        ← 抽象方法                  │
│      void move(int dx, int dy) {  ← 具体方法                  │
│          x += dx; y += dy;                                   │
│      }                                                       │
│  }                                                           │
│                                                              │
│  接口:                                                       │
│  interface Drawable {                                        │
│      void draw();                    ← 抽象方法               │
│      default void preview() { ... }  ← 默认方法              │
│  }                                                           │
│                                                              │
│  内存视角:                                                    │
│  抽象类子类: Object Header + Shape 字段 + 子类字段 + vtable   │
│  接口实现类: Object Header + 父类字段 + 子类字段 + vtable     │
│           (接口的 default 方法在 vtable 中占据槽位)         │
└─────────────────────────────────────────────────────────────┘

四、实战验证

4.1 抽象类 vs 接口的演进对比

java
// JDK 7 风格
interface OldStyle {
    void method();  // 只有抽象方法
}

// JDK 8 风格
interface NewStyle {
    void method();
    default void log(String msg) {
        System.out.println("[LOG] " + msg);
    }
    static NewStyle create() {
        return () -> System.out.println("Impl");
    }
}

4.2 典型选择场景

java
// ✅ 用抽象类:模板方法
abstract class AbstractAnimal {
    protected String name;
    AbstractAnimal(String name) { this.name = name; }
    abstract void makeSound();
    void introduce() {
        System.out.println("I'm " + name);
        makeSound();
    }
}

// ✅ 用户接口:契约
interface Flyable {
    void fly();
}
interface Swimmable {
    void swim();
}

// ✅ 同时使用
class Duck extends AbstractAnimal implements Flyable, Swimmable {
    Duck(String name) { super(name); }
    void makeSound() { System.out.println("Quack!"); }
    public void fly() { System.out.println("Flying..."); }
    public void swim() { System.out.println("Swimming..."); }
}

五、面试视角

追问答案要点
抽象类和接口的根本区别?语义:is-a vs can-do;状态:支持字段 vs 不支持;继承:单 vs 多
JDK8 为什么引入 default 方法?接口演进兼容性:在已有接口加方法而不破坏已有实现
接口中的变量为什么是 public static final?接口不能有实例状态,static final 表示常量级契约
JDK9 接口的 private 方法有什么用?提取 default 方法的公共逻辑,但不能对外暴露
什么时候用接口而非抽象类?需要多重继承、纯能力契约、不关心状态时

📚 相关链接

  • **三大特性(封装继承多态)** — 多态与接口的关系
  • **注解体系** — 注解也是一种接口
  • **注解体系速查** — 接口在框架中的大量应用
  • ← 返回 **OOP索引**

Knowledge4J — Java 知识库