跳至主要內容

06. Java抽象类

LiuSongLing大约 4 分钟javajava

在某些情况下,比如执行合同时,我们希望推迟执行合同的某些部分,以便稍后完成。

类似这样的操作,我们可以通过抽象类在Java中轻松完成。

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

1.抽象类

抽象类的特征:

  • 我们用类关键字 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
    }
}

2.何时使用抽象类

我们可以举例一些经典场景,在这类场景中,往往使用抽象类,好过使用接口和具体类:

  • 我们希望将一些通用功能封装在一个地方(代码重用),多个相关子类将共享

  • 我们需要部分定义一个API,我们的子类可以轻松扩展和细化

  • 子类需要继承一个或多个具有受保护访问修饰符的常见方法或字段

上述所有这些场景都是完全、基于继承的遵守 开放/封闭原则 的良好例子。

此外,由于使用抽象类隐式处理基类型和子类型,我们也在利用 多态性

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







3.使用示例

为了更清楚的了解抽象类如何运用,这里展示一个使用范例以供了解。

3.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可能看起来没有必要。然而,这是干净、易于扩展的设计的基础。

从中,我们可以轻松实现不同版本的文件阅读器,这些文件阅读器可以专注于其独特的业务逻辑。

3.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();
    }
}

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

3.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);
}