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[]にあります。