Javaにおける多態性

1概要

すべてのオブジェクト指向プログラミング(OOP)言語は、抽象化、カプセル化、継承、およびポリモーフィズムという4つの基本特性を示すことが必要です。

この記事では、2つのコア型のポリモーフィズム、つまり 静的またはコンパイル時ポリモーフィズム 動的またはランタイム ポリモーフィズム** __について説明します。

静的多相はコンパイル時に強制され、動的多相は実行時に実現されます。

2. 静的多型

https://en.wikipedia.org/wiki/Template metaprogramming#Static polymorphism[Wikipedia]によると、静的多型はコンパイル時に解決されるため、実行時の仮想テーブル検索 が不要になる 多型の模倣です。

たとえば、ファイルマネージャアプリケーションの 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仮想マシン(JVM)はサブクラスがその親形式に割り当てられたときに実行する適切なメソッドの検出を処理します。サブクラスは、親クラスで定義されているメソッドの一部または全部をオーバーライドする可能性があるため、これは必要です。

架空のファイルマネージャアプリケーションで、 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 の実際の形式への参照を保持しています。

  • 上記の構文はメソッドのオーバーライドに似ています** 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. 多相パラメータ

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

public class TextFile extends GenericFile {
    private String content;

    public String setContentDelimiter() {
        int content = 100;
        this.content = this.content + content;
    }
}
  • 多相パラメータの宣言は、 変数隠蔽 として知られる問題を引き起こす可能性があることに注意することも重要です。ここで、パラメータのローカル宣言は、同じ名前の別のパラメータのグローバル宣言を常にオーバーライドします。

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

4.4. 多型サブタイプ

多型サブタイプを使用すると、1つのタイプに複数のサブタイプを割り当て、そのタイプに対するすべての呼び出しによってサブタイプ内で使用可能な定義がトリガーされることを期待できます。

たとえば、 __GenericFile sのコレクションがあり、それぞれに対して 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();
}
  • サブタイプ多型は アップキャストと遅延バインディング** の組み合わせによって可能になります。アップキャストには、スーパータイプからサブタイプへの継承階層のキャストが含まれます。

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

上記の結果として生じる影響は、 __ImageFile - 固有のメソッドが新しいupcast GenericFile__上で呼び出すことができないということです。ただし、サブタイプのメソッドは、スーパータイプで定義されている類似のメソッドをオーバーライドします。

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

ImageFile imageFile = (ImageFile) file;
  • 遅延バインディング 戦略は、コンパイラがアップキャスト後にどのメソッドをトリガーするかを解決するのに役立ちます** 。上記の例のi __mageFile#getInfo file#getInfo の場合、コンパイラは ImageFile s getInfo__メソッドへの参照を保持します。

5多態性に関する問題

適切にチェックしないとランタイムエラーを引き起こす可能性がある多態性のあいまいさをいくつか見てみましょう。

5.1. ダウンキャスト中の型識別

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

たとえば、アップキャストとその後のダウンキャストを実行したとします。

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

クラスは実際には GenericFile であり ImageFile ではありませんが、コンパイラは GenericFile から ImageFile へのダウンキャストを許可しています。

したがって、 imageFile クラスで getHeight() メソッドを呼び出そうとすると、 GenericFile getHeight() メソッドを定義していないため、 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 例外を回避するのに役立ちます。

使用できるもう1つのオプションは、 try および catch ブロック内でキャストをラップし、__ClassCastExceptionをキャッチすることです。

RTTIチェックは、タイプが正しいことを効果的に検証するために必要な時間とリソースのために、高価です。さらに、 instanceof キーワードを頻繁に使用することは、デザインが悪いことを意味します。

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

https://en.wikipedia.org/wiki/Fragile base class[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 が無限の再帰になり、最終的にスタックオーバーフローが発生します。

脆弱な基本クラスの問題に対処するために、サブクラスが writeContent() メソッドをオーバーライドしないように final キーワードを使用できます。

適切なドキュメンテーションも役に立ちます。そして最後に重要なことを言い忘れていましたが、一般的には継承よりも構成が優先されるべきです。

6. 結論

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

いつものように、この記事のソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-lang-oop[]にあります。

前の投稿:The Maven Verifierプラグイン
次の投稿:Java 8の機能的インタフェース