Menu
CoddyTech

抽象类与接口的区别

本课属于 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(用于抽象方法)

一个实用的建议:如果你发现自己创建的抽象类只有抽象方法,没有状态,那么接口可能是更好的选择。

challenge icon

挑战

简单

让我们构建一个车辆系统,演示何时使用抽象类与接口。你将通过抽象类为车辆建模,让它们共享状态和行为,同时通过接口添加可选功能。

你需要将代码分布在 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() 结果(展示接口的功能)
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Java 在线编译器