Abstract Classes in Java – Java中的抽象类

最后修改: 2018年 11月 20日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

There are many cases when implementing a contract where we want to postpone some parts of the implementation to be completed later. We can easily accomplish this in Java through abstract classes.

在实现一个契约时,有很多情况下我们想把实现的某些部分推迟到以后完成。在Java中,我们可以通过抽象类轻松地实现这一点。

In this tutorial, we’ll learn the basics of abstract classes in Java, and in what cases they can be helpful.

在本教程中,我们将学习Java中的抽象类的基本知识,以及在哪些情况下它们会有帮助

2. Key Concepts for Abstract Classes

2.抽象类的关键概念

Before diving into when to use an abstract class, let’s look at their most relevant characteristics:

在深入探讨何时使用抽象类之前,让我们看看它们最相关的特征

  • We define an abstract class with the abstract modifier preceding the class keyword
  • An abstract class can be subclassed, but it can’t be instantiated
  • If a class defines one or more abstract methods, then the class itself must be declared abstract
  • An abstract class can declare both abstract and concrete methods
  • A subclass derived from an abstract class must either implement all the base class’s abstract methods or be abstract itself

To better understand these concepts, we’ll create a simple example.

为了更好地理解这些概念,我们将创建一个简单的例子。

Let’s have our base abstract class define the abstract API of a board game:

让我们让我们的基础抽象类来定义棋盘游戏的抽象API。

public abstract class BoardGame {

    //... field declarations, constructors

    public abstract void play();

    //... concrete methods
}

Then, we can create a subclass that implements the play method:

然后,我们可以创建一个子类,实现play方法。

public class Checkers extends BoardGame {

    public void play() {
        //... implementation
    }
}

3. When to Use Abstract Classes

3.何时使用抽象类

Now, let’s analyze a few typical scenarios where we should prefer abstract classes over interfaces and concrete classes:

现在,我们来分析几个典型的场景,在这些场景中,我们应该选择抽象类而不是接口和具体类。

  • We want to encapsulate some common functionality in one place (code reuse) that multiple, related subclasses will share
  • We need to partially define an API that our subclasses can easily extend and refine
  • The subclasses need to inherit one or more common methods or fields with protected access modifiers

Let’s keep in mind that all these scenarios are good examples of full, inheritance-based adherence to the Open/Closed principle.

让我们记住,所有这些情况都是完全的、基于继承的遵守开放/封闭原则的好例子

Moreover, since the use of abstract classes implicitly deals with base types and subtypes, we’re also taking advantage of Polymorphism.

此外,由于使用抽象类隐含地处理了基类型和子类型,我们也在利用Polymorphism的优势。

Note that code reuse is a very compelling reason to use abstract classes, as long as the “is-a” relationship within the class hierarchy is preserved.

请注意,代码重用是使用抽象类的一个非常有说服力的理由,只要在类的层次结构中保留 “is-a “关系。

And Java 8 adds another wrinkle with default methods, which can sometimes take the place of needing to create an abstract class altogether.

而且Java 8还增加了另一种默认方法,它有时可以取代需要完全创建一个抽象类的位置。

4. A Sample Hierarchy of File Readers

4.文件读取器的层次结构示例

To understand more clearly the functionality that abstract classes bring to the table, let’s look at another example.

为了更清楚地了解抽象类带来的功能,让我们再看一个例子。

4.1. Defining a Base Abstract Class

4.1.定义一个基础抽象类

So, if we wanted to have several types of file readers, we might create an abstract class that encapsulates what’s common to file reading:

所以,如果我们想有几种类型的文件阅读器,我们可能会创建一个抽象类,封装文件阅读的共同点。

public abstract class BaseFileReader {
    
    protected Path filePath;
    
    protected BaseFileReader(Path filePath) {
        this.filePath = filePath;
    }
    
    public Path getFilePath() {
        return filePath;
    }
    
    public List<String> readFile() throws IOException {
        return Files.lines(filePath)
          .map(this::mapFileLine).collect(Collectors.toList());
    }
    
    protected abstract String mapFileLine(String line);
}

Note that we’ve made filePath protected so that the subclasses can access it if needed. More importantly, we’ve left something undone: how to actually parse a line of text from the file’s contents.

请注意,我们已经将filePath保护起来了,这样子类就可以在需要时访问它。更重要的是,我们留下了一些未完成的工作:如何从文件的内容中实际解析一行文本

Our plan is simple: while our concrete classes don’t each have a special way to store the file path or walk through the file, they will each have a special way to transform each line.

我们的计划很简单:虽然我们的具体类没有各自的特殊方式来存储文件路径或走过文件,但它们将各自有特殊方式来转换每一行。

At first sight, BaseFileReader may seem unnecessary. However, it’s the foundation of a clean, easily extendable design. From it, we can easily implement different versions of a file reader that can focus on their unique business logic.

乍一看,BaseFileReader似乎没有必要。然而,它是一个简洁、易于扩展的设计的基础。从它出发,我们可以轻松地实现不同版本的文件阅读器,可以专注于其独特的业务逻辑

4.2. Defining Subclasses

4.2.定义子类

A natural implementation is probably one that converts a file’s contents to lowercase:

一个自然的实现可能是将文件的内容转换为小写。

public class LowercaseFileReader extends BaseFileReader {

    public LowercaseFileReader(Path filePath) {
        super(filePath);
    }

    @Override
    public String mapFileLine(String line) {
        return line.toLowerCase();
    }   
}

Or another might be one that converts a file’s contents to uppercase:

或者另一个可能是将文件内容转换为大写字母。

public class UppercaseFileReader extends BaseFileReader {

    public UppercaseFileReader(Path filePath) {
        super(filePath);
    }

    @Override
    public String mapFileLine(String line) {
        return line.toUpperCase();
    }
}

As we can see from this simple example, each subclass can focus on its unique behavior without needing to specify other aspects of file reading.

正如我们从这个简单的例子中所看到的,每个子类可以专注于其独特的行为而不需要指定文件读取的其他方面。

4.3. Using a Subclass

4.3.使用子类

Finally, using a class that inherits from an abstract one is no different than any other concrete class:

最后,使用一个继承自抽象类的类与其他具体类没有什么不同。

@Test
public void givenLowercaseFileReaderInstance_whenCalledreadFile_thenCorrect() throws Exception {
    URL location = getClass().getClassLoader().getResource("files/test.txt")
    Path path = Paths.get(location.toURI());
    BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);
        
    assertThat(lowercaseFileReader.readFile()).isInstanceOf(List.class);
}

For simplicity’s sake, the target file is located under the src/main/resources/files folder. Hence, we used an application class loader for getting the path of the example file. Feel free to check out our tutorial on class loaders in Java.

为简单起见,目标文件位于 src/main/resources/files文件夹下。因此,我们使用了一个应用程序类加载器来获取示例文件的路径。请随时查看我们关于Java中类加载器的教程

5. Conclusion

5.结论

In this quick article, we learned the basics of abstract classes in Java, and when to use them for achieving abstraction and encapsulating common implementation in one single place.

在这篇快速文章中,我们学习了Java中抽象类的基本知识,以及何时使用它们来实现抽象,并在一个地方封装共同的实现

As usual, all the code samples shown in this tutorial are available over on GitHub.

像往常一样,本教程中显示的所有代码样本都可以在GitHub上获得