###■ 多態性とは
オブジェクトをあいまいに、ざっくりと捉えることで、ある処理を命令したらクラスが違っても同じように振舞ってくれる仕組み
例えばある人物が書いてある紙を、そのままただの「紙」として捉えるのか、肖像画という「絵」として捉えるのか、お金という「紙幣」として捉えるのか、その捉え方によって同じ「使う」という振る舞いでも内容変わってくる、というようなイメージ
曖昧で抽象的なほど利用方法は限定され、逆に具体的に捉えるほどに用途は増えていく
通常のインスタンス化では
Athlete a = new Athlete();
という記述で Athlete クラスのオブジェクトを new するが、内容は Athlete 型の変数 a に、new してインスタンス化した Athlete オブジェクトを代入し、変数 a を使用するようになっている
つまり Athlete オブジェクトであれば、そのオブジェクトを 「Athlete という性質」のものとして捉えることになるが、多態性という機能はこのオブジェクトの捉え方をよりざっくり(抽象的に)としたものにすることにより様々な機能が使えるようになるというもの
Human a = new Athlete();
本当は 「 Athlete という性質」のオブジェクトだが、あくまで 「 Human という性質」のもの として捉えて利用する
Javaではextends や implememts を用いた継承の関係にあるクラス同士について、is-a の関係があると判断し、多態性を利用することができるようになる
つまり
Item a = new Athlete();
のような is-a の関係に乗っ取らない記述はそもそも継承すべきではないが、同じように多態性の利用もすべきではない
###■ 多態性の性質
Human a = new Athlete();
上記のこのインスタンス化は、new した Athlete オブジェクトを Human として捉えるということだが、そうすることによって本当は中身(オブジェクト)が Athlete であるが、 「Human の一種である何か」という、これまた曖昧な捉え方をJavaはするため、Athlete クラスでのみ定義されているメソッドが使えなくなる(正確に Athlete クラスと認識できていないため、Human として最低限出来ることしかできなくなる)
冒頭に書いた紙幣のイメージに照らし合わせると、紙幣を絵として捉えて使用しているために、本来紙幣が持つ「何かを手に入れるときに対価として支払う」という機能が使えない、ということになる
以下、例
public abstract class Human {
public void greet() {
System.out.println("人間です。");
}
}
public class Athlete extends Human {
public void introduce() {
System.out.println("アスリートです。");
}
}
Human h = new Athlete();
h.introduce();
Athlete クラスで定義した introduce メソッドは Human 型の変数 h に入れられたことで外部から明確に認識できなくなったため使用できない
「メソッド introduce() は型 Human で未定義です」とエラー
この場合、Human は「箱の型」、Athlete は「中身の型」といえる
箱の型は今回のように Human クラスに指定すれば Human として捉え、例えば Human の前のインターフェース、Life に指定すれば Life として捉える、つまり様々な箱に入れ替えることで捉え方を変えることができる 階層が汎化すればするほど、そのオブジェクトを曖昧な捉え方をすることになる
中身の型は、仮にどの箱に入っていたとしてもJavaが外部から明確に認識できなくなるだけでその性質自体が変わることはない
そのため、以下
public abstract class Human {
public void greet() {
System.out.println("人間です。");
}
}
public class Athlete extends Human {
public void greet() {
System.out.println("アスリートです。")
public void main(String[] args) {
Human h = new Athlete();
h.greet();
}
この場合は、Athlete クラスと Human クラスの両方に同じ greet メソッドがあるため、Human 型の変数 h に入れてもメソッドが呼び出せるが、この時の処理結果は Human クラスで記述されている「人間です。」ではなく、Athlete クラスで記述されている「アスリートです。」が表示される
つまり仕組みとしては 箱の型である Human クラスの greet メソッドが呼び出せるから実行が可能だが、実行できる場合には中身の型である Athlete クラスの本来のメソッドが呼び出されることになる
つまり
箱の型:どのメソッドを「呼ぶことができるか」を決定
中身の型:メソッドが呼ばれた場合「どのように処理するか」を決定する
ここで多態性の「曖昧に、ざっくり捉える」ということがメリットとなってくることがわかる
各サブクラスでメソッドのオーバーライドをしていれば、必ずサブクラスにも該当のメソッドが存在することになるため、 main メソッドで同じ命令(同じメソッドの呼び出し)を行っても、各サブクラスでオーバーライドしたそれぞれ異なる振る舞いが実行されることになる
呼び出す側は同一視し、呼び出される側は各々決められた動きを行う、という仕組み
public interface Employee {
public abstract void greet();
}
public class EmployeeSales implements Employee {
@Override
public void greet() {
System.out.println("こんにちは営業部。");
}
}
public class EmployeeManagement implements Employee {
@Override
public void greet() {
System.out.println("こんにちは管理部。");
}
}
public class EmpMain {
public static void main(String[] args) {
Employee e1 = new EmployeeSales();
Employee e2 = new EmployeeManagement();
e1.greet();
e2.greet();
introduce(e1);
introduce(e2);
}
static void introduce(Employee emp) {
System.out.println("以下、ポリモーフィズム");
emp.greet();
}
}
main メソッド外に introduce メソッドを用意し、インスタンス化した EmployeeSales、および EmployeeManagement を代入した変数 e1、e2 を、ざっくりとした Employee 型の変数で受け取り、emp.greet()でメソッド内で受け取った変数 e1、e2 が持つ greet メソッドを呼び出し実行する
######■ キャストによる強制的なメソッドの使用
先述した Athlete クラス独自の introduce メソッドが呼び出せないということについて
Human h = new Athlete();
Athlete a = (Athlete)h;
以上のように曖昧な箱の型に入っている中身を強制的に具体的な型に代入するということをキャストを用いて可能にできる、ダウンキャストと呼ばれる
ダウンキャストは元々 Java が代入の失敗の可能性があることを強制的に行うものであり、仮に継承の関係にないものをダウンキャストした場合には ClassCastException というエラーが発生する
インスタンスを代入可能か判定するには、instanceof という演算子を使用する
if (h intanceof Athlete) {
Athlete a = (Athlete)h;
a.introduction();
}