抽象类与接口的区别
本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 38 课,共 87 课。
现在你已经了解了抽象类和接口,一个常见的问题是:什么时候该用抽象类,什么时候该用接口?这取决于你想要建模的内容。
当多个类共享一个包含共同状态或行为的基类时,使用抽象类。抽象类可以包含实例变量、构造函数,以及抽象方法和具体方法:
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 makeSound(); // 必须实现
}当你想定义一种可供彼此无关的类共享的能力时,可以使用接口。接口关注对象能做什么,而不是对象是什么:
public interface Flyable {
void fly();
}
// 互不相关的类也可以拥有这种能力
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }下面是一个快速对比:
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 实例变量 | 有 | 仅常量 |
| 构造函数 | 有 | 没有 |
| 多重继承 | 不支持(只能 extends 一个类) | 支持(可以 implements 多个接口) |
| 访问修饰符 | 任意 | 仅 public(用于抽象方法) |
一个实用的建议:如果你发现自己创建的抽象类只有抽象方法,没有状态,那么接口可能是更好的选择。
挑战
简单让我们构建一个车辆系统,演示何时使用抽象类与接口。你将通过抽象类为车辆建模,让它们共享状态和行为,同时通过接口添加可选功能。
你需要将代码分布在 5 个文件中:
Vehicle.java:创建一个抽象类,作为所有车辆的基础。每辆车都有一个brand字段(String)和一个year字段(int)。添加一个构造函数来初始化这两个字段,为每个字段添加 getter 方法,并添加一个返回 String 的抽象方法startEngine()。另外,添加一个具体方法getInfo(),使其返回:[brand] ([year])。这是抽象类的理想使用场景:车辆共享状态(brand、year)和一些行为(getInfo),但每辆车启动引擎的方式都不同。Convertible.java:为可以开合车顶的车辆定义一个接口。这项功能与车辆的类型无关,而是某些车辆可以做的事。声明两个方法:openRoof()和closeRoof(),两者都返回 String。Car.java:创建一个继承Vehicle并实现Convertible的类。Car 还有一个额外的numDoors字段(int)。使用super初始化继承来的字段。实现startEngine(),使其返回:[brand] car engine started。实现openRoof(),使其返回:[brand] roof opening;实现closeRoof(),使其返回:[brand] roof closing。Motorcycle.java:创建一个继承Vehicle但不实现Convertible的类:摩托车没有车顶!Motorcycle 有一个hasSidecar字段(boolean)。实现startEngine(),使其返回:[brand] motorcycle engine roaring。Main.java:把所有内容组合起来,展示抽象类和接口之间的区别。你会收到 4 个输入:汽车品牌、汽车年份、摩托车品牌和摩托车年份。创建一辆有 4 个车门的 Car,以及一辆没有侧车的 Motorcycle。首先,打印两辆车的
getInfo(),展示抽象类提供的共享行为。它们都从 Vehicle 继承了这个方法。然后分别打印每种车辆的startEngine()结果,看看不同车辆类型是如何以不同方式实现这个方法的。最后,展示接口提供的功能:由于只有 Car 实现了
Convertible,因此只对汽车调用openRoof(),打印结果,然后调用并打印closeRoof()的结果。
你会收到 4 个输入:汽车品牌(String)、汽车年份(int)、摩托车品牌(String)和摩托车年份(int)。
你的输出总共应有 6 行。注意,两辆车共享抽象类中的状态和 getInfo() 方法,但只有 Car 具备敞篷功能。这说明了何时该使用哪种方式!
亲手试试
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入
String carBrand = scanner.nextLine();
int carYear = scanner.nextInt();
scanner.nextLine(); // 读取并丢弃换行符
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO:创建一辆有 4 扇车门的 Car
// TODO:创建一辆没有边车的 Motorcycle(false)
// TODO:打印两辆车的 getInfo() 结果(展示共享的抽象类行为)
// TODO:打印两辆车的 startEngine() 结果(展示不同的实现)
// TODO:只打印汽车的 openRoof() 和 closeRoof() 结果(展示接口的功能)
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Java 在线编译器