首页 / Java 入门教程 / 接口基础(interface)

Java 入门教程

接口基础(interface)

本教程共 100 篇 · 第 45 篇 · 更新于 2026-08-05 · 约 5 分钟阅读

JavaJava 入门教程接口interfaceimplements面向接口编程

45. 接口基础(interface)

本节目标:理解接口是一份「能力清单」,学会用 interface 声明、用 implements 实现,并搞清楚接口和抽象类到底该怎么选。

接口是什么

抽象类解决的是「is-a」关系:学生一种人,圆一种图形。

但现实里还有很多「has-a / can-do」关系:一个类「会飞」「会游泳」「能被比较大小」。这些能力往往跨物种——鸟会飞,飞机也会飞;鱼会游泳,鸭子也会游泳。

如果用继承来表达「会飞」,就得让鸟和飞机都去继承一个 Flyable 父类,可鸟明明还该继承「动物」。Java 又是单继承的,一个类只能有一个父类,没法同时「是动物」又「是飞行物」。

接口(interface)就是为这种场景设计的:它表达的是「具备某种能力」,而不是「属于某一类」。

声明一个接口

接口用 interface 关键字定义。里面通常放一组方法声明:

interface Flyable {
    void fly();          // 会飞
}
```bash

> [!NOTE]
> 在接口里,方法默认就是 `public abstract`(公开且抽象),所以这两处修饰符可以省略,写出来和下面等价:
> ```java
> public abstract void fly();
> ```
>**强烈建议省略**,这是 Java 的约定写法,更清爽,也不会因为少写一个修饰符而困惑。

## 用 implements 实现接口

类用 `implements` 关键字「实现」接口,然后必须把接口里的方法都补上实现:

```java
class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("小鸟扑棱翅膀飞");
    }
}

class Plane implements Flyable {
    @Override
    public void fly() {
        System.out.println("飞机靠发动机飞");
    }
}

只要实现了 Flyable,就说明「我有 fly 这个能力」,外界就可以把它当 Flyable 来用:

Flyable f1 = new Bird();
Flyable f2 = new Plane();
f1.fly();
f2.fly();
```bash

## 接口弥补单继承

接口最大的杀手锏:**一个类可以同时实现多个接口**。这正好补上了单继承的短板。

```java
interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

class Duck implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("鸭子飞不高");
    }

    @Override
    public void swim() {
        System.out.println("鸭子会游泳");
    }
}

Duck 既能飞又能游。如果用抽象类,这种情况就很难表达,因为 Duck 顶多继承一个父类。

Tip

记住一句口诀:继承表达「是什么」(is-a),接口表达「能干什么」(can-do)。当一个关系用「是」说不通、用「会」才顺口时,就用接口。

接口里的字段是常量

接口里也可以写字段,但它默认是 public static final——也就是常量,必须在一行里直接赋值:

interface Flyable {
    int MAX_HEIGHT = 10000;   // 等价于 public static final int MAX_HEIGHT = 10000;

    void fly();
}
```bash

因为本质是常量,所以接口不适合放会变化的状态。需要「状态」的共享数据,请放进类或枚举,别塞进接口。

> [!WARNING]
> 接口**不能实例化**(`new Flyable()` 编译报错),也不能有普通实例字段。接口只描述「能力约定」,不保存对象数据。这一点和抽象类不同——抽象类可以有普通字段和构造方法。

## 接口也能继承接口

接口和接口之间可以用 `extends` 继承,组合出更大的能力集合:

```java
interface Animal {
    void eat();
}

interface Bird extends Animal {
    void fly();
}

这样 Bird 同时拥有 eat()fly() 两个能力,实现 Bird 的类要把两个方法都实现。注意:这里用的是 extends,不是 implements——接口继承接口用 extends,类实现接口才用 implements

完整可运行示例

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

class Duck implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("鸭子飞");
    }

    @Override
    public void swim() {
        System.out.println("鸭子游");
    }
}

public class Main {
    public static void main(String[] args) {
        Duck d = new Duck();
        d.fly();
        d.swim();
    }
}
```bash

## 接口 vs 抽象类,怎么选

这是初学者最高频的问题,给一个实用的判断清单:

- 要表达「是什么」、并且需要保存**状态(字段)**、还能提供部分公共实现 → 用**抽象类**
- 要表达「能干什么」、希望一个类**同时具备多种能力** → 用**接口**
- 需要被多类「混入」一份能力约定,而不关心彼此的继承关系 → 用**接口**

> [!NOTE]
> 在 Java 8 之前,接口里只能有抽象方法。从 Java 8 起,接口允许写 `default` 方法和 `static` 方法(可以有方法体);从 Java 9 起还能有 `private` 方法。下一章我们就专门讲接口里的默认方法和静态方法,看看它们解决了什么实际问题。

## 把接口当「类型」来用

接口不只是给类打标签,它最常见的用法是作为**方法的参数类型或返回值类型**。比如你写一个方法,只关心「对方能不能飞」,不关心它到底是鸟还是飞机:

```java
void letItFly(Flyable f) {
    f.fly();
}

调用时,凡是实现了 Flyable 的对象都能传进去:

letItFly(new Bird());
letItFly(new Plane());
```bash

这就把「具体类型」和「使用方」解耦了:以后新增一个 `Helicopter` 实现 `Flyable`,`letItFly` 一行都不用改。接口在这里充当了「最小契约」——使用方只依赖它,不依赖任何具体类。

> [!TIP]
> 当你发现方法参数想写成某个父类、但几个互不相关的类都该能传进来时,就该抽一个公共接口当参数类型,而不是强行拉一个共同父类。

## 接口成员的默认修饰符

接口里的成员都有「隐含身份」,你不写编译器也会替你补上。这张表建议记牢:

| 你写的 | 编译器补全后 | 说明 |
|--------|--------------|------|
| `void fly();` | `public abstract void fly();` | 抽象方法 |
| `int MAX = 10;` | `public static final int MAX = 10;` | 常量,必须当场初始化 |
| `default void a() {}` | `public default void a() {}` | 默认方法(Java 8+|
| `static void b() {}` | `public static void b() {}` | 静态方法(Java 8+|

有两个坑要提前打招呼。

接口里**不能用 `private` 或 `protected` 修饰抽象方法**——接口本来就是给外界看的约定,藏起来毫无意义。

实现类重写接口方法时**必须写 `public`**,漏写就等于降级成包级权限,编译器立刻翻脸:

```text
error: fly() in Bird cannot implement fly() in Flyable
  attempting to assign weaker access privileges; was public

这是初学者最常撞的一堵墙。接口方法本来是 public,实现时的访问权限只能相同、不能更小。

常见疑问

Q:一个类能同时继承父类又实现接口吗?

能,而且很常见。写法是 class Duck extends Animal implements Flyable, Swimmableextends 必须写在 implements 前面,父类只能有一个,接口可以列多个。

Q:两个接口有同名抽象方法,实现类会冲突吗?

不会。抽象方法只有签名没有实现,实现类写一份就同时满足两边的约定。真正会打架的是两个接口都带了默认实现的情况,下一章细讲。

小结

  • 接口用 interface 定义,方法默认 public abstract,字段默认 public static final 常量。
  • 类用 implements 实现接口,必须实现里面所有抽象方法。
  • 一个类可以实现多个接口,弥补了单继承的不足。
  • 接口不能实例化,也不保存对象状态。
  • 接口之间可以用 extends 继承,组合能力。
  • 选接口还是抽象类:is-a 用抽象类,can-do 用接口。