interface.md 22.5 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1
---
沉默王二's avatar
沉默王二 已提交
2 3
title: Java接口,看这一篇就够了,简单易懂
shortTitle: Java接口
沉默王二's avatar
沉默王二 已提交
4
description: Java接口是面向对象编程的一种关键机制,它允许不同的类通过接口实现相同的方法,从而提高代码的灵活性和可扩展性。本文详细介绍了Java接口的定义、使用场景、新特性(如默认方法、静态方法和私有方法)以及实现方式,助您深入理解接口在Java编程中的重要性。
沉默王二's avatar
沉默王二 已提交
5
category:
沉默王二's avatar
沉默王二 已提交
6
  - Java 核心
沉默王二's avatar
沉默王二 已提交
7
tag:
沉默王二's avatar
沉默王二 已提交
8 9 10 11
  - 面向对象编程
head:
  - - meta
    - name: keywords
沉默王二's avatar
沉默王二 已提交
12
      content: Java,接口,java interface
沉默王二's avatar
沉默王二 已提交
13
---
沉默王二's avatar
沉默王二 已提交
14

沉默王二's avatar
沉默王二 已提交
15
# 5.11 Java接口
沉默王二's avatar
沉默王二 已提交
16

沉默王二's avatar
沉默王二 已提交
17
“今天开始讲 Java 的接口。”我对三妹说,“对于面向对象编程来说,抽象是一个极具魅力的特征。如果一个程序员的抽象思维很差,那他在编程中就会遇到很多困难,无法把业务变成具体的代码。在 Java 中,可以通过两种形式来达到抽象的目的,一种上一篇的主角——[抽象类](https://javabetter.cn/oo/abstract.html),另外一种就是今天的主角——[接口](https://javabetter.cn/oo/interface.html)。”
沉默王二's avatar
沉默王二 已提交
18 19

“二哥,开讲之前,先恭喜你呀。我看你朋友圈说《[Java进阶之路](https://github.com/itwanger/toBeBetterJavaer)》开源知识库在 GitHub 上收到了第一笔赞赏呀,虽然只有一块钱,但我也替你感到开心。”三妹的脸上洋溢着自信的微笑,仿佛这钱是打给她的一样。
沉默王二's avatar
沉默王二 已提交
20

沉默王二's avatar
沉默王二 已提交
21
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/interface-01.png)
沉默王二's avatar
沉默王二 已提交
22

沉默王二's avatar
沉默王二 已提交
23 24
>PS:2021-04-29到2023-02-11期间,《二哥的 Java 进阶之路》收到了 58 笔赞赏,真的非常感谢大家的认可和支持😍,我会继续肝下去的。

沉默王二's avatar
沉默王二 已提交
25 26 27 28
“是啊,早上起来的时候看到这条信息,还真的是挺开心的,虽然只有一块钱,但是开源的第一笔,也是我人生当中的第一笔,真的非常感谢这个读者,值得纪念的一天。”我自己也掩饰不住内心的激动。

“有了这份鼓励,我相信你更新下去的动力更足了!”三妹今天说的话真的是特别令人喜欢。

沉默王二's avatar
沉默王二 已提交
29
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/interface-02.png)
沉默王二's avatar
沉默王二 已提交
30

沉默王二's avatar
沉默王二 已提交
31
“是呀是呀,让我们开始吧!”
沉默王二's avatar
沉默王二 已提交
32

沉默王二's avatar
沉默王二 已提交
33
### 01、定义接口
沉默王二's avatar
沉默王二 已提交
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

“接口是什么呀?”三妹顺着我的话题及时的插话到。

接口通过 interface 关键字来定义,它可以包含一些常量和方法,来看下面这个示例。

```java
public interface Electronic {
    // 常量
    String LED = "LED";

    // 抽象方法
    int getElectricityUse();

    // 静态方法
    static boolean isEnergyEfficient(String electtronicType) {
        return electtronicType.equals(LED);
    }

    // 默认方法
    default void printDescription() {
        System.out.println("电子");
    }
}
```

来看一下这段代码反编译后的字节码。

```java
public interface Electronic
{

    public abstract int getElectricityUse();

    public static boolean isEnergyEfficient(String electtronicType)
    {
        return electtronicType.equals("LED");
    }

    public void printDescription()
    {
        System.out.println("\u7535\u5B50");
    }

    public static final String LED = "LED";
}
```

发现没?接口中定义的所有变量或者方法,都会自动添加上 `public` 关键字。

接下来,我来一一解释下 Electronic 接口中的核心知识点。

沉默王二's avatar
沉默王二 已提交
85
**1)接口中定义的变量会在编译的时候自动加上 `public static final` 修饰符**(注意看一下反编译后的字节码),也就是说上例中的 LED 变量其实就是一个常量。
沉默王二's avatar
沉默王二 已提交
86 87 88 89 90 91 92 93 94

Java 官方文档上有这样的声明:

>Every field declaration in the body of an interface is implicitly public, static, and final.

换句话说,接口可以用来作为常量类使用,还能省略掉 `public static final`,看似不错的一种选择,对吧?

不过,这种选择并不可取。因为接口的本意是对方法进行抽象,而常量接口会对子类中的变量造成命名空间上的“污染”。

沉默王二's avatar
沉默王二 已提交
95
**2)没有使用 `private`、`default` 或者 `static` 关键字修饰的方法是隐式抽象的**,在编译的时候会自动加上 `public abstract` 修饰符。也就是说上例中的 `getElectricityUse()` 其实是一个抽象方法,没有方法体——这是定义接口的本意。
沉默王二's avatar
沉默王二 已提交
96

沉默王二's avatar
沉默王二 已提交
97
**3)从 Java 8 开始,接口中允许有静态方法**,比如说上例中的 `isEnergyEfficient()` 方法。
沉默王二's avatar
沉默王二 已提交
98 99 100 101 102

静态方法无法由(实现了该接口的)类的对象调用,它只能通过接口名来调用,比如说 `Electronic.isEnergyEfficient("LED")`

接口中定义静态方法的目的是为了提供一种简单的机制,使我们不必创建对象就能调用方法,从而提高接口的竞争力。

沉默王二's avatar
nio  
沉默王二 已提交
103
**4)接口中允许定义 `default` 方法**也是从 Java 8 开始的,比如说上例中的 `printDescription()` 方法,它始终由一个代码块组成,为实现该接口而不覆盖该方法的类提供默认实现。既然要提供默认实现,就要有方法体,换句话说,默认方法后面不能直接使用“;”号来结束——编译器会报错。
沉默王二's avatar
沉默王二 已提交
104

沉默王二's avatar
沉默王二 已提交
105
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/interface-03.png)
沉默王二's avatar
沉默王二 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119

“为什么要在接口中定义默认方法呢?”三妹好奇地问到。

允许在接口中定义默认方法的理由很充分,因为一个接口可能有多个实现类,这些类就必须实现接口中定义的抽象类,否则编译器就会报错。假如我们需要在所有的实现类中追加某个具体的方法,在没有 `default` 方法的帮助下,我们就必须挨个对实现类进行修改。

由之前的例子我们就可以得出下面这些结论:

- 接口中允许定义变量
- 接口中允许定义抽象方法
- 接口中允许定义静态方法(Java 8 之后)
- 接口中允许定义默认方法(Java 8 之后)

除此之外,我们还应该知道:

沉默王二's avatar
沉默王二 已提交
120
**1)接口不允许直接实例化**,否则编译器会报错。
沉默王二's avatar
沉默王二 已提交
121

沉默王二's avatar
沉默王二 已提交
122
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/interface-04.png)
沉默王二's avatar
沉默王二 已提交
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145

需要定义一个类去实现接口,见下例。

```java
public class Computer implements Electronic {

    public static void main(String[] args) {
        new Computer();
    }

    @Override
    public int getElectricityUse() {
        return 0;
    }
}
```

然后再实例化。

```
Electronic e = new Computer();
```

沉默王二's avatar
沉默王二 已提交
146
**2)接口可以是空的**,既可以不定义变量,也可以不定义方法。最典型的例子就是 Serializable 接口,在 `java.io` 包下。
沉默王二's avatar
沉默王二 已提交
147 148 149 150 151 152 153 154

```java
public interface Serializable {
}
```

Serializable 接口用来为序列化的具体实现提供一个标记,也就是说,只要某个类实现了 Serializable 接口,那么它就可以用来序列化了。

沉默王二's avatar
沉默王二 已提交
155
**3)不要在定义接口的时候使用 final 关键字**,否则会报编译错误,因为接口就是为了让子类实现的,而 final 阻止了这种行为。
沉默王二's avatar
沉默王二 已提交
156

沉默王二's avatar
沉默王二 已提交
157
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/interface-05.png)
沉默王二's avatar
沉默王二 已提交
158

沉默王二's avatar
沉默王二 已提交
159
**4)接口的抽象方法不能是 private、protected 或者 final**,否则编译器都会报错。
沉默王二's avatar
沉默王二 已提交
160

沉默王二's avatar
沉默王二 已提交
161
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/interface-06.png)
沉默王二's avatar
沉默王二 已提交
162

沉默王二's avatar
沉默王二 已提交
163 164 165
**5)接口的变量是隐式 `public static final`(常量)**,所以其值无法改变。

### 02、接口的作用
沉默王二's avatar
沉默王二 已提交
166 167 168

“接口可以做什么呢?”三妹见缝插针,问的很及时。

沉默王二's avatar
沉默王二 已提交
169
**第一,使某些实现类具有我们想要的功能**,比如说,实现了 Cloneable 接口的类具有拷贝的功能,实现了 Comparable 或者 Comparator 的类具有比较功能。
沉默王二's avatar
沉默王二 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

Cloneable 和 Serializable 一样,都属于标记型接口,它们内部都是空的。实现了 Cloneable 接口的类可以使用 `Object.clone()` 方法,否则会抛出 CloneNotSupportedException。

```java
public class CloneableTest implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableTest c1 = new CloneableTest();
        CloneableTest c2 = (CloneableTest) c1.clone();
    }
}
```

运行后没有报错。现在把 `implements Cloneable` 去掉。

```java
public class CloneableTest {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableTest c1 = new CloneableTest();
        CloneableTest c2 = (CloneableTest) c1.clone();

    }
}
```

运行后抛出 CloneNotSupportedException:

```
Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTest
	at java.base/java.lang.Object.clone(Native Method)
	at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)
	at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)
```


沉默王二's avatar
沉默王二 已提交
214
**第二,Java 原则上只支持单一继承,但通过接口可以实现多重继承的目的**
沉默王二's avatar
沉默王二 已提交
215 216 217

如果有两个类共同继承(extends)一个父类,那么父类的方法就会被两个子类重写。然后,如果有一个新类同时继承了这两个子类,那么在调用重写方法的时候,编译器就不能识别要调用哪个类的方法了。这也正是著名的菱形问题,见下图。

沉默王二's avatar
沉默王二 已提交
218
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/interface-07.png)
沉默王二's avatar
沉默王二 已提交
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251


简单解释下,ClassC 同时继承了 ClassA 和 ClassB,ClassC 的对象在调用 ClassA 和 ClassB 中重写的方法时,就不知道该调用 ClassA 的方法,还是 ClassB 的方法。

接口没有这方面的困扰。来定义两个接口,Fly 接口会飞,Run 接口会跑。

```java
public interface Fly {
    void fly();
}
public interface Run {
    void run();
}
```

然后让 Pig 类同时实现这两个接口。

```java
public class Pig implements Fly,Run{
    @Override
    public void fly() {
        System.out.println("会飞的猪");
    }

    @Override
    public void run() {
        System.out.println("会跑的猪");
    }
}
```

在某种形式上,接口实现了多重继承的目的:现实世界里,猪的确只会跑,但在雷军的眼里,站在风口的猪就会飞,这就需要赋予这只猪更多的能力,通过抽象类是无法实现的,只能通过接口。

沉默王二's avatar
沉默王二 已提交
252
**第三,实现多态**
沉默王二's avatar
沉默王二 已提交
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306

什么是多态呢?通俗的理解,就是同一个事件发生在不同的对象上会产生不同的结果,鼠标左键点击窗口上的 X 号可以关闭窗口,点击超链接却可以打开新的网页。

多态可以通过继承(`extends`)的关系实现,也可以通过接口的形式实现。

Shape 接口表示一个形状。

```java
public interface Shape {
    String name();
}
```

Circle 类实现了 Shape 接口,并重写了 `name()` 方法。

```java
public class Circle implements Shape {
    @Override
    public String name() {
        return "圆";
    }
}
```

Square 类也实现了 Shape 接口,并重写了 `name()` 方法。

```java
public class Square implements Shape {
    @Override
    public String name() {
        return "正方形";
    }
}
```

然后来看测试类。

```java
List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();

shapes.add(circleShape);
shapes.add(squareShape);

for (Shape shape : shapes) {
    System.out.println(shape.name());
}
```

这就实现了多态,变量 circleShape、squareShape 的引用类型都是 Shape,但执行 `shape.name()` 方法的时候,Java 虚拟机知道该去调用 Circle 的 `name()` 方法还是 Square 的 `name()` 方法。

说一下多态存在的 3 个前提:

沉默王二's avatar
沉默王二 已提交
307 308 309
- 1、要有继承关系,比如说 Circle 和 Square 都实现了 Shape 接口。
- 2、子类要重写父类的方法,Circle 和 Square 都重写了 `name()` 方法。
- 3、父类引用指向子类对象,circleShape 和 squareShape 的类型都为 Shape,但前者指向的是 Circle 对象,后者指向的是 Square 对象。
沉默王二's avatar
沉默王二 已提交
310 311 312 313 314 315 316 317 318 319

然后,我们来看一下测试结果:

```

正方形
```

也就意味着,尽管在 for 循环中,shape 的类型都为 Shape,但在调用 `name()` 方法的时候,它知道 Circle 对象应该调用 Circle 类的 `name()` 方法,Square 对象应该调用 Square 类的 `name()` 方法。

沉默王二's avatar
沉默王二 已提交
320
### 03、接口的三种模式
沉默王二's avatar
沉默王二 已提交
321 322 323

**在编程领域,好的设计模式能够让我们的代码事半功倍**。在使用接口的时候,经常会用到三种模式,分别是策略模式、适配器模式和工厂模式。

沉默王二's avatar
沉默王二 已提交
324
#### 1)策略模式
沉默王二's avatar
沉默王二 已提交
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368

策略模式的思想是,针对一组算法,将每一种算法封装到具有共同接口的实现类中,接口的设计者可以在不影响调用者的情况下对算法做出改变。示例如下:

```java
// 接口:教练
interface Coach {
    // 方法:防守
    void defend();
}

// 何塞·穆里尼奥
class Hesai implements Coach {

    @Override
    public void defend() {
        System.out.println("防守赢得冠军");
    }
}

// 德普·瓜迪奥拉
class Guatu implements Coach {

    @Override
    public void defend() {
        System.out.println("进攻就是最好的防守");
    }
}

public class Demo {
    // 参数为接口
    public static void defend(Coach coach) {
        coach.defend();
    }
    
    public static void main(String[] args) {
        // 为同一个方法传递不同的对象
        defend(new Hesai());
        defend(new Guatu());
    }
}
```

`Demo.defend()` 方法可以接受不同风格的 Coach,并根据所传递的参数对象的不同而产生不同的行为,这被称为“策略模式”。

沉默王二's avatar
沉默王二 已提交
369
#### 2)适配器模式
沉默王二's avatar
沉默王二 已提交
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402

适配器模式的思想是,针对调用者的需求对原有的接口进行转接。生活当中最常见的适配器就是HDMI(英语:`High Definition Multimedia Interface`,中文:高清多媒体接口)线,可以同时发送音频和视频信号。适配器模式的示例如下:

```java
interface Coach {
    void defend();
    void attack();
}

// 抽象类实现接口,并置空方法
abstract class AdapterCoach implements Coach {
    public void defend() {};
    public void attack() {};
}

// 新类继承适配器
class Hesai extends AdapterCoach {
    public void defend() {
        System.out.println("防守赢得冠军");
    }
}

public class Demo {
    public static void main(String[] args) {
        Coach coach = new Hesai();
        coach.defend();
    }
}
```
Coach 接口中定义了两个方法(`defend()``attack()`),如果类直接实现该接口的话,就需要对两个方法进行实现。

如果我们只需要对其中一个方法进行实现的话,就可以使用一个抽象类作为中间件,即适配器(AdapterCoach),用这个抽象类实现接口,并对抽象类中的方法置空(方法体只有一对花括号),这时候,新类就可以绕过接口,继承抽象类,我们就可以只对需要的方法进行覆盖,而不是接口中的所有方法。

沉默王二's avatar
沉默王二 已提交
403
#### 3)工厂模式
沉默王二's avatar
沉默王二 已提交
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472

所谓的工厂模式理解起来也不难,就是什么工厂生产什么,比如说宝马工厂生产宝马,奔驰工厂生产奔驰,A 级学院毕业 A 级教练,C 级学院毕业 C 级教练。示例如下:

```java
// 教练
interface Coach {
    void command();
}

// 教练学院
interface CoachFactory {
    Coach createCoach();
}

// A级教练
class ACoach implements Coach {

    @Override
    public void command() {
        System.out.println("我是A级证书教练");
    }
    
}

// A级教练学院
class ACoachFactory implements CoachFactory {

    @Override
    public Coach createCoach() {
        return new ACoach();
    }
    
}

// C级教练
class CCoach implements Coach {

    @Override
    public void command() {
        System.out.println("我是C级证书教练");
    }
    
}

// C级教练学院
class CCoachFactory implements CoachFactory {

    @Override
    public Coach createCoach() {
        return new CCoach();
    }
    
}

public class Demo {
    public static void create(CoachFactory factory) {
        factory.createCoach().command();
    }
    
    public static void main(String[] args) {
        // 对于一支球队来说,需要什么样的教练就去找什么样的学院
        // 学院会介绍球队对应水平的教练。
        create(new ACoachFactory());
        create(new CCoachFactory());
    }
}
```

有两个接口,一个是 Coach(教练),可以 `command()`(指挥球队);另外一个是 CoachFactory(教练学院),能 `createCoach()`(教出一名优秀的教练)。然后 ACoach 类实现 Coach 接口,ACoachFactory 类实现 CoachFactory 接口;CCoach 类实现 Coach 接口,CCoachFactory 类实现 CoachFactory 接口。当需要 A 级教练时,就去找 A 级教练学院;当需要 C 级教练时,就去找 C 级教练学院。
沉默王二's avatar
沉默王二 已提交
473

沉默王二's avatar
沉默王二 已提交
474
依次类推,我们还可以用 BCoach 类实现 Coach 接口,BCoachFactory 类实现 CoachFactory 接口,从而不断地丰富教练的梯队。
沉默王二's avatar
沉默王二 已提交
475

沉默王二's avatar
沉默王二 已提交
476 477 478 479 480 481
“怎么样三妹,一下子接收这么多知识点不容易吧?”

“其实还好啊,二哥你讲的这么细致,我都做好笔记📒了,学习嘛,认真一点,效果就会好很多了。”

三妹这种积极乐观的态度真的让我感觉到“付出就会有收获”,💪🏻。

沉默王二's avatar
沉默王二 已提交
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
### 04、抽象类和接口的区别

简单总结一下抽象类和接口的区别。

在 Java 中,通过关键字 `abstract` 定义的类叫做抽象类。Java 是一门面向对象的语言,因此所有的对象都是通过类来描述的;但反过来,并不是所有的类都是用来描述对象的,抽象类就是其中的一种。

以下示例展示了一个简单的抽象类:

```java
// 个人认为,一名教练必须攻守兼备
abstract class Coach {
	public abstract void defend();

	public abstract void attack();
}
```

我们知道,有抽象方法的类被称为抽象类,也就意味着抽象类中还能有不是抽象方法的方法。这样的类就不能算作纯粹的接口,尽管它也可以提供接口的功能——只能说抽象类是普通类与接口之间的一种中庸之道。

**接口(英文:Interface),在 Java 中是一个抽象类型,是抽象方法的集合**;接口通过关键字 `interface` 来定义。接口与抽象类的不同之处在于:

- 1、抽象类可以有方法体的方法,但接口没有(Java 8 以前)。
- 2、接口中的成员变量隐式为 `static final`,但抽象类不是的。
- 3、一个类可以实现多个接口,但只能继承一个抽象类。

以下示例展示了一个简单的接口:

```java
// 隐式的abstract
interface Coach {
	// 隐式的public
	void defend();
	void attack();
}
```

- 接口是隐式抽象的,所以声明时没有必要使用 `abstract` 关键字;
- 接口的每个方法都是隐式抽象的,所以同样不需要使用 `abstract` 关键字;
- 接口中的方法都是隐式 `public` 的。

“哦,我理解了哥。那我再问一下,抽象类和接口有什么差别呢?”

“哇,三妹呀,你这个问题恰到好处,问到了点子上。”我不由得为三妹竖起了大拇指。

#### 1)语法层面上

- 抽象类可以提供成员方法的实现细节,而接口中只能存在 public abstract 方法;
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
- 接口中不能含有静态代码块,而抽象类可以有静态代码块;
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

#### 2)设计层面上

抽象类是对一种事物的抽象,即对类抽象,继承抽象类的子类和抽象类本身是一种 `is-a` 的关系。而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类 Airplane,将鸟设计为一个类 Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。

此时可以将 飞行 设计为一个接口 Fly,包含方法 fly(),然后 Airplane 和 Bird 分别根据自己的需要实现 Fly 这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承 Airplane 即可,对于鸟也是类似的,不同种类的鸟直接继承 Bird 类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

沉默王二's avatar
沉默王二 已提交
541
接口是对类的某种行为的一种抽象,接口和类之间并没有很强的关联关系,举个例子来说,所有的类都可以实现 [`Serializable` 接口](https://javabetter.cn/io/Serializbale.html),从而具有序列化的功能,但不能说所有的类和 Serializable 之间是 `is-a` 的关系。
沉默王二's avatar
沉默王二 已提交
542 543

抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过 ppt 里面的模板,如果用模板 A 设计了 ppt B 和 ppt C,ppt B 和 ppt C 公共的部分就是模板 A 了,如果它们的公共部分需要改动,则只需要改动模板 A 就可以了,不需要重新对 ppt B 和 ppt C 进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
沉默王二's avatar
沉默王二 已提交
544

沉默王二's avatar
沉默王二 已提交
545
----
沉默王二's avatar
沉默王二 已提交
546 547


沉默王二's avatar
9000+  
沉默王二 已提交
548
GitHub 上标星 9000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 9000+ 的 Java 教程](https://javabetter.cn/overview/)
沉默王二's avatar
沉默王二 已提交
549

沉默王二's avatar
沉默王二 已提交
550

沉默王二's avatar
沉默王二 已提交
551
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
沉默王二's avatar
沉默王二 已提交
552

沉默王二's avatar
沉默王二 已提交
553
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)