Java 抽象类

更新于 2025-12-25

Jakob Jenkov 2015-03-09

Java 抽象类(abstract class)是一种不能被实例化的类,也就是说你不能创建抽象类的新实例。抽象类的用途是作为子类的基类。本篇 Java 抽象类教程将解释如何在 Java 中创建抽象类、适用于抽象类的规则,并在文末更详细地探讨抽象类的设计目的。

声明一个 Java 抽象类

在 Java 中,通过在类声明前加上 abstract 关键字来声明一个抽象类。下面是一个 Java 抽象类的示例:

public abstract class MyAbstractClass {
}

这就是在 Java 中声明抽象类的全部内容。现在你无法再创建 MyAbstractClass 的实例了。因此,以下 Java 代码将不再有效:

MyAbstractClass myClassInstance = new MyAbstractClass(); // 无效

如果你尝试编译上面的代码,Java 编译器会报错,提示你不能实例化 MyAbstractClass,因为它是抽象类。

抽象方法

抽象类可以包含抽象方法。通过在方法声明前加上 abstract 关键字来声明一个抽象方法。下面是一个 Java 抽象方法的示例:

public abstract class MyAbstractClass {
    public abstract void abstractMethod();
}

抽象方法没有具体实现,它只有方法签名,就像 Java 接口 中的方法一样。

如果一个类包含抽象方法,那么整个类必须被声明为抽象类。但并不是抽象类中的所有方法都必须是抽象的——抽象类可以同时包含抽象方法和非抽象方法。

抽象类的子类必须实现(重写)其抽象父类中的所有抽象方法。父类中的非抽象方法则会被直接继承,如有需要也可以被重写。

下面是一个 MyAbstractClass 抽象类的子类示例:

public class MySubClass extends MyAbstractClass {
    public void abstractMethod() {
        System.out.println("My method implementation");
    }
}

注意,MySubClass 必须实现其抽象父类 MyAbstractClass 中的抽象方法 abstractMethod()

唯一一种子类不需要实现其抽象父类中所有抽象方法的情况是:该子类本身也是一个抽象类。

抽象类的用途

抽象类的目的是作为基类,供子类扩展以完成完整实现。例如,假设某个处理过程包含以下三个步骤:

  1. 执行操作前的步骤。
  2. 执行操作本身。
  3. 执行操作后的步骤。

如果“操作前”和“操作后”的步骤总是相同的,那么这个三步流程可以在一个抽象超类中实现,如下所示:

public abstract class MyAbstractProcess {
    public void process() {
        stepBefore();
        action();
        stepAfter();
    }

    public void stepBefore() {
        // 在抽象超类中直接实现
    }

    public abstract void action(); // 由子类实现

    public void stepAfter() {
        // 在抽象超类中直接实现
    }
}

注意,action() 方法是抽象的。MyAbstractProcess 的子类只需继承该类并重写 action() 方法即可。

当调用子类的 process() 方法时,完整的流程会被执行,包括抽象超类中的 stepBefore()stepAfter(),以及子类中实现的 action() 方法。

当然,MyAbstractProcess 并不一定非要定义成抽象类才能作为基类使用,action() 方法也不一定非得是抽象的。你完全可以使用一个普通类。但通过将待实现的方法设为抽象方法(从而使得类也变为抽象类),你向该类的使用者清晰地传达了一个信号:这个类不应该直接使用,而应该作为基类被继承,并且抽象方法应在子类中实现

上面的例子中,action() 方法没有默认实现。但在某些情况下,你的超类可能确实为子类要重写的方法提供了一个默认实现。在这种情况下,你就不能将该方法声明为抽象方法。不过,即使类中没有任何抽象方法,你仍然可以将该类声明为抽象类。

下面是一个更具体的例子:打开一个 URL,处理其内容,然后关闭与该 URL 的连接。

public abstract class URLProcessorBase {
    public void process(URL url) throws IOException {
        URLConnection urlConnection = url.openConnection();
        InputStream input = urlConnection.getInputStream();
        try {
            processURLData(input);
        } finally {
            input.close();
        }
    }

    protected abstract void processURLData(InputStream input) throws IOException;
}

注意,processURLData() 是一个抽象方法,而 URLProcessorBase 是一个抽象类。URLProcessorBase 的子类必须实现 processURLData() 方法,因为它是一个抽象方法。

通过继承 URLProcessorBase 抽象类,子类可以在无需关心打开和关闭网络连接的情况下处理从 URL 下载的数据。这部分工作由 URLProcessorBase 完成。子类只需要关注如何处理传入 processURLData() 方法的 InputStream 中的数据。这使得实现处理 URL 数据的类变得更加简单。

下面是一个子类的示例:

public class URLProcessorImpl extends URLProcessorBase {
    @Override
    protected void processURLData(InputStream input) throws IOException {
        int data = input.read();
        while (data != -1) {
            System.out.println((char) data);
            data = input.read();
        }
    }
}

注意,这个子类只实现了 processURLData() 方法,其余代码都继承自 URLProcessorBase 超类。

下面是使用 URLProcessorImpl 类的一个示例:

URLProcessorImpl urlProcessor = new URLProcessorImpl();
urlProcessor.process(new URL("https://www.mianshima.com"));

这里调用了 process() 方法,该方法实现在 URLProcessorBase 超类中。该方法内部又会调用 URLProcessorImpl 类中的 processURLData() 方法。

抽象类与模板方法设计模式

前面展示的 URLProcessorBase 类实际上就是 模板方法(Template Method)设计模式 的一个例子。模板方法设计模式提供了一个流程的部分实现,子类在继承模板方法基类时可以完成剩余的实现。