抽象类与接口

核心概念

抽象类(Abstract Class)和接口(Interface)都是Java中定义抽象层次的方式,但设计思想和使用场景不同。理解它们的区别和演进,是设计良好架构的基础。


一、抽象类(Abstract Class)

1.1 基本语法

public abstract class Animal {
    // 普通字段
    protected String name;

    // 构造函数(不能直接实例化,但子类可以调用)
    public Animal(String name) {
        this.name = name;
    }

    // 普通方法
    public void sleep() {
        System.out.println(name + " is sleeping");
    }

    // 抽象方法(没有方法体,子类必须实现)
    public abstract void speak();
}

// 子类必须实现所有抽象方法
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println(name + " says: Woof!");
    }
}

1.2 抽象类的特点

特点 说明
不能实例化 new Animal() 编译错误
可以有构造函数 供子类调用
可以有普通方法 提供默认实现
可以有抽象方法 子类必须实现
可以有字段 任意访问修饰符
单继承 只能extends一个类

1.3 抽象类的设计意图

// 模板方法模式:定义算法骨架,子类实现具体步骤
public abstract class AbstractOrderProcessor {
    // 模板方法(final防止子类修改流程)
    public final void processOrder(Order order) {
        validateOrder(order);
        calculatePrice(order);
        applyDiscount(order);  // 抽象方法,子类实现
        saveOrder(order);
        sendNotification(order);  // 钩子方法,子类可选重写
    }

    private void validateOrder(Order order) { /* 通用验证 */ }
    private void calculatePrice(Order order) { /* 通用计算 */ }
    private void saveOrder(Order order) { /* 通用保存 */ }

    // 抽象方法:子类必须实现
    protected abstract void applyDiscount(Order order);

    // 钩子方法:子类可选重写
    protected void sendNotification(Order order) {
        // 默认实现:发送邮件
    }
}

// 具体实现
public class VIPOrderProcessor extends AbstractOrderProcessor {
    @Override
    protected void applyDiscount(Order order) {
        order.setDiscount(0.8);  // VIP 8折
    }

    @Override
    protected void sendNotification(Order order) {
        // VIP专属通知
    }
}

二、接口(Interface)

2.1 JDK 7及之前

public interface Flyable {
    // 常量(默认 public static final)
    int MAX_SPEED = 1000;

    // 抽象方法(默认 public abstract)
    void fly();
    void land();
}

// 实现类
public class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying");
    }

    @Override
    public void land() {
        System.out.println("Bird is landing");
    }
}

2.2 JDK 8接口增强

public interface Flyable {
    // 抽象方法
    void fly();

    // 默认方法(default):提供默认实现,实现类可选重写
    default void takeOff() {
        System.out.println("Taking off...");
    }

    // 静态方法(static):通过接口名调用
    static void showInfo() {
        System.out.println("This is Flyable interface");
    }
}

// 使用
Bird bird = new Bird();
bird.fly();      // 实现类方法
bird.takeOff();  // 默认方法

Flyable.showInfo();  // 静态方法

2.3 JDK 9接口增强

public interface Flyable {
    void fly();

    default void takeOff() {
        prepare();  // 调用私有方法
        System.out.println("Taking off...");
    }

    default void land() {
        System.out.println("Landing...");
        cleanup();  // 调用私有方法
    }

    // 私有方法(private):提取公共逻辑,仅接口内部使用
    private void prepare() {
        System.out.println("Preparing...");
    }

    private void cleanup() {
        System.out.println("Cleaning up...");
    }

    // 私有静态方法
    private static void log(String msg) {
        System.out.println("[LOG] " + msg);
    }
}

2.4 接口演进总结

版本 新增特性 说明
JDK 7 常量、抽象方法 纯抽象
JDK 8 default方法、static方法 可以有实现
JDK 9 private方法、private static方法 代码复用

三、抽象类 vs 接口

3.1 核心区别

特性 抽象类 接口
继承 单继承 多实现
构造函数 可以有 不能有
字段 任意类型 只能public static final
方法 任意类型 public(JDK 9+可private)
设计思想 is-a(是什么) can-do(能做什么)
演进成本 低(可加普通方法) 高(JDK 8前加方法会破坏实现类)

3.2 设计思想对比

接口更像是一种行为规范,强调“能做什么”抽象类更像是一种模板,强调“应该怎么做”

从设计思路上来说:接口通常是自上而下的设计,我们先定义好一个行为契约(比如 Runnable),然后不同的类根据自己的能力去实现它。这种方式适合“能力横切”的场景(像排序、序列化),这些类之间未必有继承关系。这种设计强调先约定行为,再去实现,面向接口编程就是这个思想的体现

抽象类的设计更偏向于自下而上,我们往往是写了一批类之后,发现它们之间有一些通用逻辑或状态,于是把这些共性抽象出来,形成一个抽象父类,减少重复代码。比如所有动物都有名字、有吃饭的方法,那就可以抽出一个 Animal 抽象类。

另外一个比较关键区别在于:Java 支持 接口的多实现**,不支持抽象类的多继承。**

这是因为 Java 要避免多继承带来的复杂性,特别是菱形继承问题,这种问题很容易引起成员冲突和继承歧义,所以一个类只能继承一个抽象类。但接口就没这种问题,接口里没有普通的字段状态,它的变量都是 static final 的,也不依赖构造方法,天生就是用来声明行为的,所以可以允许多实现,不会有状态冲突,一个类可以实现多个接口。(没有状态、不依赖构造器,自然避免多继承冲突)

从语法角度补充几点:

  • 接口只能用 implements 关键字来实现,抽象类只能用 extends 关键字来单继承。
  • 接口里的方法默认是 public 的,字段是 public static final 的。
  • 抽象类可以有各种访问修饰符、普通字段和构造器。
  • 抽象类里可以写通用逻辑,接口主要声明行为规范,在 Java 8+ 得到了增强(default、static、private 方法),在保留行为约定特性的同时也具备了部分逻辑复用能力,但依然不能有构造函数,不能持有实例状态,这也是它与抽象类的根本区别。

在选择上:

  • 如果是多个无关类要统一某个能力,优先接口(如 Comparable、Serializable)。
  • 如果是多个相关类共享部分代码逻辑,适合抽象类。

一句话总结:接口强调“行为契约”,适合多实现;抽象类强调“逻辑复用”,只适合单继承。

// 抽象类:is-a 关系(是什么)
abstract class Animal {
    protected String name;
    public abstract void speak();
}

class Dog extends Animal {
    // Dog is an Animal
}

// 接口:can-do 关系(能做什么)
interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

class Duck extends Animal implements Flyable, Swimmable {
    // Duck is an Animal
    // Duck can fly
    // Duck can swim
}

3.3 选择原则

需要定义一组行为规范?
    ├── 是 → 接口
    └── 否 → 需要共享代码或状态?
                ├── 是 → 抽象类
                └── 否 → 需要多继承?
                            ├── 是 → 接口
                            └── 否 → 根据语义选择

选择接口的场景:

  • 定义能力/行为(Comparable、Serializable)
  • 需要多继承
  • 不相关的类需要实现相同行为

选择抽象类的场景:

  • 有共享的代码或状态
  • 需要非public成员
  • 需要构造函数
  • 紧密相关的类之间共享实现

3.4 组合使用

// 接口定义能力
public interface Drawable {
    void draw();
}

// 抽象类提供部分实现
public abstract class Shape implements Drawable {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    // 留给子类实现
    @Override
    public abstract void draw();
}

// 具体类
public class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing " + color + " circle with radius " + radius);
    }
}

四、接口的默认方法冲突

4.1 类优先原则

interface A {
    default void method() {
        System.out.println("A");
    }
}

class B {
    public void method() {
        System.out.println("B");
    }
}

class C extends B implements A {
    // 不需要重写,类的方法优先
}

new C().method();  // 输出:B

4.2 接口冲突

interface A {
    default void method() {
        System.out.println("A");
    }
}

interface B {
    default void method() {
        System.out.println("B");
    }
}

// 必须重写解决冲突
class C implements A, B {
    @Override
    public void method() {
        A.super.method();  // 选择调用A的默认方法
        // 或 B.super.method();
        // 或自己实现
    }
}

4.3 冲突解决规则

  1. 类优先:类中的方法优先于接口默认方法
  2. 子接口优先:更具体的接口优先
  3. 显式选择:如果仍有冲突,必须重写并显式选择

五、函数式接口

5.1 定义

// 只有一个抽象方法的接口
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);

    // 可以有默认方法
    default Comparator<T> reversed() {
        return (o1, o2) -> compare(o2, o1);
    }

    // 可以有静态方法
    static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (o1, o2) -> o1.compareTo(o2);
    }
}

5.2 常用函数式接口

接口 方法 用途
Runnable void run() 无参无返回
Supplier<T> T get() 无参有返回
Consumer<T> void accept(T t) 有参无返回
Function<T,R> R apply(T t) 有参有返回
Predicate<T> boolean test(T t) 判断

5.3 Lambda表达式

// 传统写法
Comparator<String> c1 = new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
};

// Lambda写法
Comparator<String> c2 = (o1, o2) -> o1.length() - o2.length();

// 方法引用
Comparator<String> c3 = Comparator.comparingInt(String::length);

六、高频面试题

Q1: 抽象类和接口有什么区别?

答:

特性 抽象类 接口
继承 单继承 多实现
构造函数
字段 任意 public static final
设计思想 is-a can-do

Q2: JDK 8对接口做了哪些增强?

答:

  • default方法:提供默认实现,解决接口演进问题
  • static方法:工具方法,通过接口名调用
  • 函数式接口:支持Lambda表达式

Q3: 什么时候用抽象类,什么时候用接口?

答:

  • 使用接口
    1. 定义行为规范
    2. 需要多继承
    3. 不相关的类需要共享行为
    4. 轻量级的行为定义
  • 使用抽象类
    1. 需要共享代码和状态
    2. 有共同的基础实现
    3. 需要非public成员
    4. 需要构造函数
    5. 紧密相关的类

Q4: 接口的默认方法解决了什么问题?

答:

  • 演进问题:在不破坏现有实现的情况下,为接口添加新方法
  • 代码复用:避免在所有实现类中重复编写相同逻辑
  • 减少抽象类使用:提供类似抽象类的代码共享能力

Q5: 为什么Java不支持多继承?

答:

  • 菱形继承问题:多个父类有同名方法,子类无法确定调用哪个
  • 复杂性:增加语言复杂度和编译器实现难度
  • 替代方案:接口 + 组合模式

Q6: 函数式接口的作用是什么?

答:

  • 只有一个抽象方法的接口
  • 支持Lambda表达式和方法引用
  • 简化匿名内部类的写法
  • 函数式编程的基础

Q7: 接口的静态方法和默认方法有什么区别?

答:

  • 静态方法
    1. 通过接口名调用
    2. 不能被实现类重写
    3. 属于接口本身
  • 默认方法
    1. 实例方法
    2. 可以被实现类重写
    3. 提供默认实现

七、最佳实践

✅ 推荐做法

// 1. 接口定义清晰的行为规范
public interface Playable {
    void play();

    // 提供通用实现
    default void pause() {
        System.out.println("Pausing...");
    }
}

// 2. 抽象类提供通用实现
public abstract class AbstractPlayer implements Playable {
    protected String name;

    public AbstractPlayer(String name) {
        this.name = name;
    }

    // 部分实现
    public void displayInfo() {
        System.out.println("Player: " + name);
    }
}

// 3. 组合优于继承
public class MusicPlayer {
    private Playable device;  // 组合

    public void setDevice(Playable device) {
        this.device = device;
    }
}

// 4. 使用函数式接口简化代码
public interface Validator<T> {
    boolean validate(T t);

    // 组合验证
    default Validator<T> and(Validator<T> other) {
        return t -> this.validate(t) && other.validate(t);
    }
}

// Lambda表达式
Validator<String> notEmpty = s -> !s.isEmpty();
Validator<String> minLength = s -> s.length() >= 6;
Validator<String> passwordValidator = notEmpty.and(minLength);

❌ 避免做法

// 1. 过度使用接口
public interface Everything {
    void doSomething();
    void doSomethingElse();
    // 接口膨胀,职责不清
}

// 2. 滥用默认方法
public interface DatabaseOperation {
    default void connect() {
        // 太多细节实现
    }

    default void disconnect() {
        // 接口不应该包含太多实现
    }
}

// 3. 忽视接口冲突
public class BadImplementation implements A, B {
    // 没有解决方法冲突
}

八、设计模式应用

8.1 策略模式

// 定义策略接口
public interface PaymentStrategy {
    void pay(int amount);
}

// 具体策略实现
public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " by credit card");
    }
}

public class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " by PayPal");
    }
}

// 上下文类
public class PaymentContext {
    private PaymentStrategy strategy;

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void executePayment(int amount) {
        strategy.pay(amount);
    }
}

// 使用
PaymentContext context = new PaymentContext();
context.setStrategy(new CreditCardPayment());
context.executePayment(100);

8.2 模板方法模式

// 抽象类定义算法骨架
public abstract class DataProcessor {
    // 模板方法
    public final void process() {
        readData();
        processData();
        saveData();
    }

    // 公共方法
    private void readData() {
        System.out.println("Reading data...");
    }

    private void saveData() {
        System.out.println("Saving data...");
    }

    // 抽象方法,子类实现
    protected abstract void processData();
}

// 具体实现
public class CSVProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Processing CSV data");
    }
}

public class JSONProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Processing JSON data");
    }
}

相关链接


记忆口诀

抽象类可有状态,接口定义能力
默认方法解演进,静态方法类级别
单继承多实现,is-a和can-do分明
组合优于继承,设计模式显神明

相关笔记

本篇是主战场(语法+设计取舍);抽象类与接口的区别 做了逐条对比速记,抽象类与接口的结合 补了模板方法模式与选型标准,可以当本篇的延伸阅读。


⬅️ 继承与多态 🏠 00-Java ➡️ 访问权限