1. 概述
在实现某种契约时,我们常常希望将部分逻辑的实现推迟到后续阶段。在 Java 中,抽象类(abstract class)就是为此而生的一种机制。
本文将带你快速了解 Java 中抽象类的基础知识,并分析其适用场景。
2. 抽象类的核心概念
在深入使用抽象类之前,先明确几个关键点:
- ✅ 抽象类需要使用
abstract
关键字修饰类声明 - ❌ 抽象类不能被直接实例化
- ⚠️ 如果一个类中包含一个或多个抽象方法(abstract method),那么这个类必须声明为抽象类
- ✅ 抽象类可以同时包含抽象方法和具体方法(concrete method)
- ⚠️ 继承自抽象类的子类必须实现其所有抽象方法,否则该子类也必须声明为抽象类
我们通过一个简单的例子来加深理解。
定义一个抽象类 BoardGame
来表示棋盘游戏的通用接口:
public abstract class BoardGame {
//... 字段声明、构造方法等
public abstract void play();
//... 具体方法
}
然后定义一个子类 Checkers
实现 play()
方法:
public class Checkers extends BoardGame {
public void play() {
//... 具体实现
}
}
3. 何时使用抽象类
那么,什么时候我们应该优先选择抽象类而不是接口或普通类呢?
以下是一些典型场景:
- ✅ 需要在多个相关子类中复用某些通用逻辑
- ✅ 需要部分定义一个 API,让子类可以扩展和细化
- ✅ 子类需要继承一些带有
protected
访问权限的字段或方法
这些场景都体现了基于继承的 开闭原则(Open/Closed Principle) 的完整实现。
此外,由于抽象类天然支持基类与子类之间的类型关系,我们也能自然地利用 多态(Polymorphism) 特性。
⚠️ 需要注意的是,代码复用虽然是使用抽象类的一个强大理由,但前提是类之间存在“is-a”关系。
✅ 另外,Java 8 引入的默认方法(default method) 有时也可以替代抽象类的部分功能。
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
设为 protected
,方便子类访问。更重要的是,我们将如何处理每一行文本的逻辑留给了子类去实现。
虽然 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 类加载器详解。
5. 小结
本文简要介绍了 Java 中抽象类的基本用法和适用场景,强调了其在实现抽象、封装通用逻辑方面的优势。
一如既往,文中所有代码均可在 GitHub 项目 中找到。