1. 概要

契約を実施する際に、実施の一部を延期して後日完了する場合が多くあります。 これは、抽象クラスを介してJavaで簡単に実現できます。

このチュートリアルでは、Javaの抽象クラスの基本と、それらがどのような場合に役立つかについて学習します。

2. 抽象クラスの重要な概念

抽象クラスをいつ使用するかを説明する前に、最も関連性の高い特性を見てみましょう。

  • classキーワードの前にabstract修飾子を付けて抽象クラスを定義します
  • 抽象クラスはサブクラス化できますが、インスタンス化することはできません
  • クラスが1つ以上の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. 抽象クラスを使用する場合

ここで、インターフェイスや具象クラスよりも抽象クラスを優先する必要があるいくつかの典型的なシナリオを分析しましょう。

  • 複数の関連するサブクラスが共有するいくつかの一般的な機能を1つの場所にカプセル化する(コードの再利用)
  • サブクラスが簡単に拡張および改良できるAPIを部分的に定義する必要があります
  • サブクラスは、保護されたアクセス修飾子を持つ1つ以上の一般的なメソッドまたはフィールドを継承する必要があります

これらのシナリオはすべて、オープン/クローズド原則への完全な継承ベースの順守の良い例であることに注意してください。

さらに、抽象クラスの使用は暗黙的に基本タイプとサブタイプを処理するため、ポリモーフィズムも利用しています。

クラス階層内の「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のクラスローダーに関するチュートリアルをお気軽にチェックしてください。

5. 結論

この簡単な記事では、 Javaでの抽象クラスの基本と、それらを使用して抽象化を実現し、共通の実装を1か所にカプセル化するタイミングを学びました

いつものように、このチュートリアルに示されているすべてのコードサンプルは、GitHubからで入手できます。