Java中的抽象类
1. 概述
在执行合同时,有很多情况下我们希望将执行的某些部分推迟到以后完成。我们可以通过抽象类在 Java 中轻松实现这一点。 在本教程中,我们将学习 Java 中抽象类的基础知识,以及它们在哪些情况下会有所帮助。
2. 抽象类的关键概念
在深入探讨何时使用抽象类之前,让我们看看它们最相关的特征:
- 我们在class关键字之前使用abstract修饰符定义一个抽象类
- 抽象类可以被子类化,但不能被实例化
- 如果一个类定义了一个或多个abstract方法,那么这个类本身必须声明为abstract
- 抽象类可以声明抽象方法和具体方法
- 从抽象类派生的子类必须要么实现所有基类的抽象方法,要么本身就是抽象的
为了更好地理解这些概念,我们将创建一个简单的示例。 让我们的基础抽象类定义棋盘游戏的抽象 API:
public abstract class BoardGame {
//... field declarations, constructors
public abstract void play();
//... concrete methods
}
然后,我们可以创建一个实现play方法的子类:
public class Checkers extends BoardGame {
public void play() {
//... implementation
}
}
3. 何时使用抽象类
现在,让我们分析一些典型的场景,我们应该更喜欢抽象类而不是接口和具体类:
- 我们希望将一些通用功能封装在一个地方(代码重用),多个相关子类将共享这些功能
- 我们需要部分定义一个 API,我们的子类可以轻松扩展和改进
- 子类需要继承一个或多个带有受保护访问修饰符的常用方法或字段
让我们记住,所有这些场景都是完全的、基于继承的、遵循Open/Closed 原则 的好例子。 此外,由于抽象类的使用隐含地处理基类型和子类型,我们还利用了多态性 。 请注意,代码重用是使用抽象类的一个非常有说服力的理由,只要保留类层次结构中的“is-a”关系。
Java 8在默认方法方面增加了另一个问题 ,有时可以完全取代创建抽象类的需要。
4. 文件阅读器的示例层次结构
为了更清楚地理解抽象类带来的功能,让我们看另一个例子。
4.1. 定义基本抽象类
所以,如果我们想要有几种类型的文件阅读器,我们可能会创建一个抽象类来封装文件阅读的常见内容:
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);
}
请注意,我们已经使filePath受到保护,以便子类可以在需要时访问它。更重要的是,我们留下了一些未完成的事情:如何从文件内容中实际解析一行文本。
我们的计划很简单:虽然我们的具体类都没有一种特殊的方式来存储文件路径或遍历文件,但它们每个都有一种特殊的方式来转换每一行。
乍一看,BaseFileReader似乎没有必要。但是,它是干净、易于扩展的设计的基础。从中, 我们可以轻松实现不同版本的文件阅读器,可以专注于其独特的业务逻辑。
4.2. 定义子类
一种自然的实现可能是将文件的内容转换为小写:
public class LowercaseFileReader extends BaseFileReader {
public LowercaseFileReader(Path filePath) {
super(filePath);
}
@Override
public String mapFileLine(String line) {
return line.toLowerCase();
}
}
或者另一种可能是将文件的内容转换为大写的:
public class UppercaseFileReader extends BaseFileReader {
public UppercaseFileReader(Path filePath) {
super(filePath);
}
@Override
public String mapFileLine(String line) {
return line.toUpperCase();
}
}
正如我们从这个简单的示例中看到的,每个子类都可以专注于其独特的行为,而无需指定文件读取的其他方面。
4.3. 使用子类
最后,使用继承自抽象类的类与任何其他具体类没有什么不同:
@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);
}
为简单起见,目标文件位于src/main/resources/files 文件夹下。因此,我们使用应用程序类加载器来获取示例文件的路径。查看Java 类加载器 教程。