Javaにおける多態性

Javaにおける多態性

1. 概要

すべてのオブジェクト指向プログラミング(OOP)言語は、抽象化、カプセル化、継承、および多態性の4つの基本的な特性を示す必要があります。

この記事では、ポリモーフィズムの2つのコアタイプである*static or compile-time polymorphism* and dynamic or runtime *polymorphism*について説明します。 静的な多型はコンパイル時に実行され、動的な多型は実行時に実現されます。

2. 静的多型

Wikipediaによると、静的ポリモーフィズムはpolymorphism which is resolved at compile time and thus does away with run-time virtual-table lookupsの模倣です。

たとえば、ファイルマネージャーアプリのTextFileクラスには、read()メソッドの同じシグネチャを持つ3つのメソッドを含めることができます。

public class TextFile extends GenericFile {
    //...

    public String read() {
        return this.getContent()
          .toString();
    }

    public String read(int limit) {
        return this.getContent()
          .toString()
          .substring(0, limit);
    }

    public String read(int start, int stop) {
        return this.getContent()
          .toString()
          .substring(start, stop);
    }
}

コードのコンパイル中に、コンパイラーは、readメソッドのすべての呼び出しが、上記で定義された3つのメソッドの少なくとも1つに対応することを確認します。

3. 動的多型

動的ポリモーフィズムでは、Java Virtual Machine (JVM) handles the detection of the appropriate method to execute when a subclass is assigned to its parent form。 これは、サブクラスが親クラスで定義されたメソッドの一部またはすべてをオーバーライドする可能性があるために必要です。

架空のファイルマネージャーアプリで、GenericFileという名前のすべてのファイルの親クラスを定義しましょう。

public class GenericFile {
    private String name;

    //...

    public String getFileInfo() {
        return "Generic File Impl";
    }
}

GenericFileを拡張するが、getFileInfo()メソッドをオーバーライドし、詳細情報を追加するImageFileクラスを実装することもできます。

public class ImageFile extends GenericFile {
    private int height;
    private int width;

    //... getters and setters

    public String getFileInfo() {
        return "Image File Impl";
    }
}

ImageFileのインスタンスを作成し、それをGenericFileクラスに割り当てると、暗黙的なキャストが実行されます。 ただし、JVMはImageFileの実際の形式への参照を保持します。

The above construct is analogous to method overriding.これは、次の方法でgetFileInfo()メソッドを呼び出すことで確認できます。

public static void main(String[] args) {
    GenericFile genericFile = new ImageFile("SampleImageFile", 200, 100,
      new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB)
      .toString()
      .getBytes(), "v1.0.0");
    logger.info("File Info: \n" + genericFile.getFileInfo());
}

予想どおり、genericFile.getFileInfo()は、以下の出力に示すように、ImageFileクラスのgetFileInfo()メソッドをトリガーします。

File Info:
Image File Impl

4. Javaの他の多形特性

Javaのこれら2つの主な多型に加えて、Javaプログラミング言語には多型を示す他の特性があります。 これらの特徴のいくつかについて説明しましょう。

4.1. 強制

多態性強制は、型エラーを防ぐためにコンパイラーによって行われる暗黙的な型変換を処理します。 典型的な例は、整数と文字列の連結に見られます。

String str = “string” + 2;

4.2. オペレータの過負荷

演算子またはメソッドのオーバーロードは、コンテキストに応じて異なる意味(フォーム)を持つ同じシンボルまたは演算子の多態的な特性を指します。

たとえば、プラス記号(+)は、数学的な加算やStringの連結に使用できます。 どちらの場合でも、コンテキストのみ(つまり、 引数の種類)記号の解釈を決定します:

String str = "2" + 2;
int sum = 2 + 2;
System.out.printf(" str = %s\n sum = %d\n", str, sum);

出力:

str = 22
sum = 4

4.3. 多型パラメータ

パラメトリック多相性により、クラス内のパラメーターまたはメソッドの名前をさまざまなタイプに関連付けることができます。 以下に、contentStringとして定義し、後でIntegerとして定義する典型的な例を示します。

public class TextFile extends GenericFile {
    private String content;

    public String setContentDelimiter() {
        int content = 100;
        this.content = this.content + content;
    }
}

また、declaration of polymorphic parameters can lead to a problem known asvariable hidingでは、パラメータのローカル宣言が常に同じ名前の別のパラメータのグローバル宣言を上書きすることに注意することも重要です。

この問題を解決するには、thisキーワードなどのグローバル参照を使用して、ローカルコンテキスト内のグローバル変数を指すことをお勧めします。

4.4. 多型サブタイプ

ポリモーフィックサブタイプにより、複数のサブタイプを1つのタイプに割り当てることができ、そのタイプでのすべての呼び出しがサブタイプで使用可能な定義をトリガーすることを期待できます。

たとえば、GenericFilesのコレクションがあり、それぞれでgetInfo()メソッドを呼び出すと、コレクション内の各アイテムが派生したサブタイプに応じて出力が異なることが予想されます。 :

GenericFile [] files = {new ImageFile("SampleImageFile", 200, 100,
  new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB).toString()
  .getBytes(), "v1.0.0"), new TextFile("SampleTextFile",
  "This is a sample text content", "v1.0.0")};

for (int i = 0; i < files.length; i++) {
    files[i].getInfo();
}

Subtype polymorphism is made possible by a combination ofupcasting and late binding。 アップキャスティングには、スーパータイプからサブタイプへの継承階層のキャストが含まれます。

ImageFile imageFile = new ImageFile();
GenericFile file = imageFile;

上記の結果として生じる影響は、ImageFile-固有のメソッドを新しいアップキャストGenericFileで呼び出すことができないことです。 ただし、サブタイプのメソッドは、スーパータイプで定義された同様のメソッドをオーバーライドします。

スーパータイプにアップキャストするときにサブタイプ固有のメソッドを呼び出せないという問題を解決するために、スーパータイプからサブタイプへの継承のダウンキャストを行うことができます。 これは以下によって行われます:

ImageFile imageFile = (ImageFile) file;

Late bindingstrategy helps the compiler to resolve whose method to trigger after upcasting。 上記の例のimageFile#getInfofile#getInfoの場合、コンパイラーはImageFilegetInfoメソッドへの参照を保持します。

5. ポリモーフィズムの問題

適切にチェックしないとランタイムエラーにつながる可能性のあるポリモーフィズムのあいまいさを見てみましょう。

5.1. ダウンキャスティング中のタイプ識別

アップキャストを実行した後、以前にいくつかのサブタイプ固有のメソッドへのアクセスを失ったことを思い出してください。 これはダウンキャストで解決できましたが、実際の型チェックを保証するものではありません。

たとえば、アップキャストとそれに続くダウンキャストを実行する場合:

GenericFile file = new GenericFile();
ImageFile imageFile = (ImageFile) file;
System.out.println(imageFile.getHeight());

クラスが実際にはImageFileではなくGenericFileである場合でも、コンパイラーはGenericFileImageFileへのダウンキャストを許可していることがわかります。

したがって、imageFileクラスでgetHeight()メソッドを呼び出そうとすると、GenericFilegetHeight()メソッドを定義しないため、ClassCastExceptionを取得します。

Exception in thread "main" java.lang.ClassCastException:
GenericFile cannot be cast to ImageFile

この問題を解決するために、JVMは実行時タイプ情報(RTTI)チェックを実行します。 次のように、instanceofキーワードを使用して、明示的な型の識別を試みることもできます。

ImageFile imageFile;
if (file instanceof ImageFile) {
    imageFile = file;
}

上記は、実行時にClassCastException例外を回避するのに役立ちます。 使用できる別のオプションは、キャストをtryおよびcatchブロック内でラップし、ClassCastException.をキャッチすることです。

タイプが正しいことを効果的に検証するために必要な時間とリソースのために、RTTI check is expensiveに注意する必要があります。 さらに、instanceofキーワードを頻繁に使用することは、ほとんどの場合、設計が不適切であることを意味します。

5.2. 壊れやすい基本クラスの問題

Wikipediaによると、基本クラスへの安全と思われる変更によって派生クラスが誤動作する可能性がある場合、基本クラスまたはスーパークラスは脆弱であると見なされます。

GenericFileと呼ばれるスーパークラスとそのサブクラスTextFileの宣言を考えてみましょう。

public class GenericFile {
    private String content;

    void writeContent(String content) {
        this.content = content;
    }
    void toString(String str) {
        str.toString();
    }
}
public class TextFile extends GenericFile {
    @Override
    void writeContent(String content) {
        toString(content);
    }
}

GenericFileクラスを変更すると:

public class GenericFile {
    //...

    void toString(String str) {
        writeContent(str);
    }
}

上記の変更により、writeContent()メソッドでTextFileが無限再帰になり、最終的にスタックオーバーフローが発生することがわかります。

壊れやすい基本クラスの問題に対処するために、finalキーワードを使用して、サブクラスがwriteContent()メソッドをオーバーライドしないようにすることができます。 適切なドキュメントも役立ちます。 最後に大事なことを言い忘れていましたが、一般的にはコンポジションが継承よりも優先されるべきです。

6. 結論

この記事では、長所と短所の両方に焦点を当てて、多型の基本概念について説明しました。

いつものように、この記事のソースコードはover on GitHubで入手できます。