ポリモフィズム(多様性)では、スーパークラスやインタフェースの型で変数を定義し、サブクラスやインタフェースを実装したクラスのインスタンスを参照します。このため、異なった機能を持つメソッドであっても、同じメソッド名で呼び出して処理を実行することが可能になります。

つまり、メソッド名を統一にすると呼び出し側が、呼び出すオブジェクトについて詳しく知らなくても、呼び出し型さえ知っていればそのオブジェクトを利用することができます。

実際には、スーパークラスの参照型変数、配列型変数、引数などで、サブクラスのインスタンスを参照できます。これによりポリモフィズムを実現し、効率的なコードの記述が可能になります。

子クラスのオブジェクトは親クラスの型と代入互換性を持つ

クラスに親子関係があると、親クラスの型の変数に子クラスの型のインスタンスを代入できます。parent.myMethod()の呼び出し結果が、ParentクラスMyClassのmyMethod()でなく、ChildクラスMySubClassのmyMethod()にバインドされます。ここでのポイントは、レイトバインディングや仮想メソッド呼び出しなどと説明されることがありますが、宣言されている変数の型ではなく、実際に変数に代入されているインスタンスの型によって、実行時の振る舞いが決まります。この機能をオーバライドと呼びます。なお、クラス変数については、オーバライドはされません。このクライスのクラス変数fieldAは、同一名称であっても、決してオーバーライドできません。ここでは、親のクラス変数fieldAがアクセスされます。

class MyClass{
	   public String fieldA = "Parent";
	void myMethod(){
		System.out.println("Hello World !!");
	}
}
class MySubClass extends MyClass{
    public String fieldA = "Child";
	void myMethod(){
		System.out.println("Hello World SubClass !!");
	}
}
public class polymorphism {
	public static void main(String[] args) {
		MyClass parent  = new MySubClass();
		System.out.println(parent.fieldA); // field A
		parent.myMethod();  //myMethod in Child
		System.out.println("***\n"); // field A
	}
}

実行すると、次のように表示されます。

Parent
Hello World SubClass !!

オーバロードは、コンパイル時にコンパイラがどのメソッドを実行すべきかを判断して、コードを作成します。オーバライドは、実行中に適切なメッソッドを選択するという、非常に動的なボリモフィズムを持っている。

引数のスーパークラスを宣言する

あるメソッドの引数の方を宣言するとき、実際に引数として渡されるオブジェクトのクラス名を指定せずに、そのクラスのスーパクラスの名前を指定できます。そして、その引数に渡されたオブジェクトのメソッドを呼ぶと、渡されたオブジェクトの実際のクラスが持つメソッドのほうが実行されます。

次の例では、callMyMethodクラスの入力パラメータは、スーパークラスのMyClassクラスが定義されています。callMyMethodメソッドのを呼び出すときの入力パラメータをMySubClassクラスとすると、実行されるのは、MySubClassクラスのmyMethodメソッドとなります。つまり、より上位のクラスとして宣言された引数の下位のクラスを渡した場合、実際に実行されるのは、下位のクラスでオーバライドしているメソッドになります。

class MyClass{
	void myMethod(){
		System.out.println("Hello World !!");
	}
}
class MySubClass extends MyClass{
	void myMethod(){
		System.out.println("Hello World SubClass !!");
	}
}
public class polymorphism {
	static void callMyMethod(MyClass arg){
		arg.myMethod();
	}
	public static void main(String[] args) {
		MySubClass mysubclass = new MySubClass();
		callMyMethod(mysubclass);
	}
}

実行すると、次のように表示されます。

Hello World SubClass !!

引数にインターフェースを宣言する

引数にインタフェースの名前だけを宣言した場合、インタフェースを直接newすることはできないので、そのメソッドを呼ぶときはインタフェースを実装したクラスを別に作って呼ぶ必要があります。実行時には、インタフェースで定義されたメソッドが実行されるのではなく、そのインタフェースを実装したクラスのメソッドが実行されます。

次の例では、callMyMethodメソッドの入力パラメータには、インタフェース名称MyInterfaceで宣言されていますが、実行時にはMyClass1 クラスのインスタンスmyclassを引数として呼び出します。

class MySubClass extends MyClass{
	void myMethod(){
		System.out.println("Hello World SubClass !!");
	}
}
interface MyInterface{
	void myMethod();
}
class MyClass1 implements MyInterface{
	public void myMethod(){
		System.out.println("Hello World 1 !!");
	}
}

public class polymorphism {
	static void callMyMethod(MyInterface arg){
		arg.myMethod();
	}
	public static void main(String[] args) {
		MyClass1 myclass = new MyClass1();
		callMyMethod(myclass);
	}
}

実行すると、次のように表示されます。

Hello World 1 !!

メソッドの戻り値をインタフェースで返す

メソッドの戻り値を、abstractクラスやインタフェースで宣言しておくことができます。そうすると、ポリモフィズム機能が働き、様々なクラスのオブジェクトが戻り値で返ってきても、そのメソッドを呼ぶことができます。
次の例では、MyInterfaceオブジェクトの戻り値を持つcreateMyInterfaceメソッドが、入力パラメータnが偶数か奇数かで、MyClase1クラスもしくはMyClase2クラスを戻します。

interface MyInterface{
	void myMethod();
}
class MyClass1 implements MyInterface{
	public void myMethod(){
		System.out.println("Hello World 1 !!");
	}
}
class MyClass2 implements MyInterface{
	public void myMethod(){
		System.out.println("Hello World 2 !!");
	}
}
public class polymorphism {
	static MyInterface createMyInterface(int n){
		if(n % 2 == 0){
			return new MyClass1();
		}
		else{
			return new MyClass2();
		}
	}
	public static void main(String[] args) {
		MyInterface myinterface1 = createMyInterface(2);
		myinterface1.myMethod();

		MyInterface myinterface2 = createMyInterface(3);
		myinterface2.myMethod();
	}
}

実行結果は、次のようになります。

Hello World 1 !!
Hello World 2 !!