接口基础(interface)
本教程共 100 篇 · 第 45 篇 · 更新于 2026-08-05 · 约 5 分钟阅读
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, Swimmable。extends 必须写在 implements 前面,父类只能有一个,接口可以列多个。
Q:两个接口有同名抽象方法,实现类会冲突吗?
不会。抽象方法只有签名没有实现,实现类写一份就同时满足两边的约定。真正会打架的是两个接口都带了默认实现的情况,下一章细讲。
小结
- 接口用
interface定义,方法默认public abstract,字段默认public static final常量。 - 类用
implements实现接口,必须实现里面所有抽象方法。 - 一个类可以实现多个接口,弥补了单继承的不足。
- 接口不能实例化,也不保存对象状态。
- 接口之间可以用
extends继承,组合能力。 - 选接口还是抽象类:is-a 用抽象类,can-do 用接口。