Javaのポリモーフィズム
1. 概要
すべてのオブジェクト指向プログラミング(OOP)言語は、抽象化、カプセル化、継承、およびポリモーフィズムの4つの基本的な特性を示す必要があります。
この記事では、静的またはコンパイル時のポリモーフィズムと動的または実行時のポリモーフィズムの2つのコアタイプのポリモーフィズムについて説明します。 静的ポリモーフィズムはコンパイル時で強制され、動的ポリモーフィズムはランタイムで実現されます。
2. 静的ポリモーフィズム
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";
}
}
ImageFile クラスを実装することもできます。これは、 GenericFile を拡張しますが、 getFileInfo()メソッドをオーバーライドし、詳細情報を追加します。
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 のコレクションがあり、それぞれで 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-固有のメソッドを新しいアップキャストGenericFileで呼び出すことができないことです。 ただし、サブタイプのメソッドは、スーパータイプで定義された同様のメソッドをオーバーライドします。
スーパータイプにアップキャストするときにサブタイプ固有のメソッドを呼び出せないという問題を解決するために、スーパータイプからサブタイプへの継承のダウンキャストを実行できます。 これは次の方法で行われます。
ImageFile imageFile = (ImageFile) file;
遅延バインディング戦略は、アップキャストの後にトリガーするメソッドをコンパイラーが解決するのに役立ちます。 上記の例のimageFile#getInfoとfile#getInfo の場合、コンパイラはImageFileのgetInfoへの参照を保持します。 ] 方法。
5. ポリモーフィズムの問題
適切にチェックしないとランタイムエラーにつながる可能性のあるポリモーフィズムのあいまいさを見てみましょう。
5.1. ダウンキャスティング中のタイプ識別
アップキャストを実行した後、以前にいくつかのサブタイプ固有のメソッドにアクセスできなくなったことを思い出してください。 ダウンキャストでこれを解決することはできましたが、これは実際の型チェックを保証するものではありません。
たとえば、アップキャストとそれに続くダウンキャストを実行する場合:
GenericFile file = new GenericFile();
ImageFile imageFile = (ImageFile) file;
System.out.println(imageFile.getHeight());
クラスが実際にはGenericFileでありImageFileではない場合でも、コンパイラがGenericFileのImageFileへのダウンキャストを許可していることがわかります。 ]。
したがって、 imageFileクラスでgetHeight()メソッドを呼び出そうとすると、 GenericFile が定義しないため、ClassCastExceptionが取得されます。 getHeight()メソッド:
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チェックは高価であることに注意してください。 さらに、 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);
}
}
上記の変更により、 TextFileがwriteContent()メソッドで無限再帰になり、最終的にスタックオーバーフローが発生することがわかります。
脆弱な基本クラスの問題に対処するために、 final キーワードを使用して、サブクラスが writeContent()メソッドをオーバーライドしないようにすることができます。 適切なドキュメントも役立ちます。 そして最後に大事なことを言い忘れましたが、一般的には継承よりも構成が優先されるべきです。
6. 結論
この記事では、長所と短所の両方に焦点を当てて、ポリモーフィズムの基本的な概念について説明しました。
いつものように、この記事のソースコードはGitHubでから入手できます。