×

抽象类讲完了。你知道父类可以只声明方法签名,不写实现体,强制子类去实现。这是一种 「模板」 的思想。

但抽象类有个问题:一个类 只能继承一个父类。如果某个类想要拥有多种能力——比如一个汽车类想同时具备「可驾驶」和「可充电」的特征——单继承的局限性就卡住了。

Java 的解决方案就是 接口(Interface)。接口只定义了「能做什么」的契约,不关心「怎么做」。而且一个类可以实现多个接口,弥补了单继承的不足。

抽象类是「是什么」的关系(is-a),接口是「能做什么」的关系(can-do)。

一、接口的语法

1.1 定义和实现一个接口

// 定义接口——用 interface 关键字,而不是 class
public interface Flyable {
    // 抽象方法(不写 abstract 也默认是)
    void fly();
}

// 实现接口——用 implements 关键字
class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("鸟儿扇动翅膀飞行");
    }
}

class Airplane implements Flyable {
    @Override
    public void fly() {
        System.out.println("飞机引擎推进飞行");
    }
}

1.2 接口与多态

接口天然支持多态——你可以声明一个接口类型的引用,指向任何实现了该接口的类对象:

Flyable f1 = new Bird();
Flyable f2 = new Airplane();

f1.fly();   // 鸟儿扇动翅膀飞行——动态绑定
f2.fly();   // 飞机引擎推进飞行——动态绑定

// 多态数组
Flyable[] sky = { new Bird(), new Airplane(), new Drone() };
for (Flyable f : sky) {
    f.fly();
}

有没有发现和抽象类很像?是的,接口的引用和抽象类的引用一样,都是靠多态在运行期决定调用哪个实现类的方法。区别在于——接口更「纯粹」,只定义行为契约,不包含任何状态。

二、接口的特性与规则

2.1 接口中的成员

成员类型默认修饰符备注
抽象方法public abstract接口的主要成员,实现类必须重写
常量public static final接口中的变量默认都是常量
默认方法(Java 8+)public default接口可以有默认实现,子类可选择是否重写
静态方法(Java 8+)public static通过接口名直接调用
私有方法(Java 9+)private辅助默认方法/静态方法,对外不可见
public interface AdvancedInterface {
    // 1. 抽象方法(最常见)
    void doSomething();

    // 2. 常量(接口中的变量都是 public static final)
    int MAX_COUNT = 100;

    // 3. 默认方法——有方法体,子类可选择是否重写
    default void doDefault() {
        System.out.println("默认实现,子类可以不重写");
        helperMethod();   // 调私有方法
    }

    // 4. 静态方法——通过接口名调用
    static void doStatic() {
        System.out.println("接口自己的静态方法");
    }

    // 5. 私有方法(JDK 9+)——给默认方法或静态方法内部复用
    private void helperMethod() {
        System.out.println("私有辅助方法,对外不可见");
    }
}

2.2 接口不能包含的东西

  • ❌ 实例变量(接口没有状态,不能有非静态字段)
  • ❌ 构造器(接口不能被实例化,当然也没有构造器)
  • ❌ 实例初始化块(没有实例,初始化块自然没意义)

2.3 接口的多继承

接口之间可以 多继承

interface A {
    void methodA();
}
interface B {
    void methodB();
}
// 接口多继承
interface C extends A, B {
    void methodC();
}

// 实现类——必须实现所有接口中的所有抽象方法
class MyClass implements C {
    @Override
    public void methodA() { /* ... */ }
    @Override
    public void methodB() { /* ... */ }
    @Override
    public void methodC() { /* ... */ }
}

这是接口和抽象类的重要区别之一——抽象类只能单继承接口可以多继承。但因为接口没有状态(没有实例变量),多继承不会引入「菱形问题」中的状态冲突。方法签名上的冲突可以通过重写或 super 语法解决。

三、接口 vs 抽象类

这是面试必问题,同时也是实际开发中最容易纠结的点。

对比项抽象类接口
关键字abstract classinterface
关系is-a(是什么)can-do(能做什么)
多继承只能单继承可以多实现、多继承
构造器没有
实例变量可以有不能有(只有常量)
方法实现可以有普通方法+抽象方法全抽象方法(Java 8+可以有默认/静态方法)
访问修饰符可以用任意访问修饰符方法默认 public
使用场景类族有共同基类,共享状态和部分实现定义行为契约,不关心具体层级

3.1 选择判断器

纠结的时候,问自己三个问题:

  1. 这两个类之间有「是什么」的关系吗?(Cat is-a Animal → 用抽象类)
  2. 多个不相关的类需要共用同一套行为吗?(Bird 和 Airplane 都能 fly → 用接口)
  3. 需要共享状态(字段)吗?(需要 → 用抽象类;不需要 → 用接口)

通常情况下,行业的共识是:优先使用接口,而不是抽象类。接口的耦合度更低,更灵活,也更容易测试和 mock。

四、default 方法的引入动机

Java 8 之前,接口里只能有抽象方法。这意味着如果给一个接口新增一个方法,所有实现类都得跟着改——即便大多数实现类的实现逻辑都一样。

Java 8 引入 default 方法的初衷:

  • 为了给 Collection 接口新增 stream()forEach() 等方法,但又不强制所有实现类重写
  • 如果不用 default 方法,Java 8 要加流式操作,就得让所有集合类(ArrayList、HashSet 等)都改代码——那场面太美不敢看
// Collection 接口中 Java 8 新增的默认方法
public interface Collection<E> {
    // 这个方法所有实现类自动就有了,不用逐个重写
    default void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        for (E e : this) {
            action.accept(e);
        }
    }
}

你看,ArrayListLinkedListHashSet……所有这些集合类,一行代码没改就拥有了 forEach() 方法。这就是 default 方法的威力。

五、总结

  • 接口是「契约」:定义行为规范,不涉及具体实现和状态
  • 接口支持多实现:弥补 Java 单继承的不足
  • 接口的成员:抽象方法(主流)+ 常量 + default 方法 + 静态方法 + 私有方法
  • 接口 vs 抽象类:is-a 用抽象类,can-do 用接口;优先接口
  • default 方法:让接口可以扩展而不破坏现有实现

这一篇是接口的基础。下一篇我们来深入接口在工程中的高级用法——标记接口、函数式接口(lambda 的前置知识)、接口隔离原则、回调与策略模式。你会发现,理解了接口,才能真正理解现代 Java 生态。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章

19. 多态的应用:参数多态化、工厂模式与支付系统实战

上一篇我们拆解了多态的底层机制——动态绑定、...

读出全部

18. 多态的本质:动态绑定、向上转型与向下转型

抽象类学完了,你现在应该知道 「父类只声明、...

读出全部