第9章 ポリモーフィズム

はじめに

本章では、オーバーライドの特質から導き出される、ポリモーフィズムについて説明します。

目次

  • 継承ツリー上のオーバーロード
  • メソッドのオーバーライド
  • 共変戻り値
  • ポリモーフィズム
  • ポリモーフィズムの利用
  • 章のまとめ

目標

ポリモーフィズムについて理解すること。

継承ツリー上のオーバーロード

同じ継承ツリー上であれば、サブクラススーパークラスメソッドオーバーロードできます。

例題(継承ツリー上のオーバーロード)

実際に作成し、実行結果を確認してましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Member
package chapter9;

public class Member {

/**
* 名前を保持
*/
String name;

/**
* 名前設定
* @param name 名前
*/
public void set(String name) {
this.name = name;
}

}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Friend
継承 Member
package chapter9;

/**
* Friendクラス
* @version 1.0
* @author Yamamoto
*/
public class Friend extends Member {

/**
* 年齢を保持
*/
int age;

/**
* 設定
* @param name 年齢
*/
public void set(int age) {
this.age = age;
}

/**
* 表示
* @param 無し
* @return 無し
*/
public void disp(){
System.out.println("名前:" + name);
System.out.println("年齢:" + age);
}

}

実行するためのクラス(Execクラス)を作成します。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Exec
package chapter9;

/**
* 実行する為のクラス
* @version 1.0
* @author Yamamoto
*/
public class Exec {

/**
* mainメソッド
*/
public static void main(String[] args) {

Friend f = new Friend();
f.set("佐藤"); //スーパークラスのset()メソッド実行
f.set(22); //サブクラスのset()メソッド実行
f.disp();
}
}

コンパイル例:

> javac chapter9¥Member.java
> javac chapter9¥Friend.java
> javac chapter9¥Exec.java

実行例:

> java chapter9.Exec
名前:佐藤
年齢:22

上記は継承ツリー上のオーバーロードの例です。Memberクラスがスーパークラス、Friendクラスがサブクラスです。
Friendクラス内のset()メソッドが、オーバーロードにあたります。

サブクラスのオーバーロード規則

サブクラスのオーバーロードには次のような規則があります。

  • 引数の構成(型、順序、数)を変えることでオーバーロードになる
  • 戻り値型やアクセス修飾子は自由に変えることができる

※引数の構成(型、順序、数)が同じだとコンパイルエラーになるので注意してください。

オーバーライド

スーパークラスとサブクラス間で、メソッド名が同じでかつ引数構成も全く同じメソッドを定義した場合、それはオーバーライド(再定義)といいます。

オーバーライドは、スーパークラスで定義したメソッドを再度サブクラス定義しなおしたい場合に、おこないます。

例題(オーバーライド)

先ほど作成しました、Memberクラス、Friendクラス、Execクラスを修正し、実行結果を確認してましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Member
package chapter9;

public class Member {

/**
* 名前を保持
*/
String name;

/**
* 名前設定
* @param name 名前
*/
public void set(String name) {
this.name = name;
}

//-----ここから(追加)-----
/**
* 表示
* @param 無し
* @return 無し
*/
public void disp(){
System.out.println("Memberクラスです。");
}
//-----ここまで(追加)-----

}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Friend
継承 Member
package chapter9;

/**
* Friendクラス
* @version 1.0
* @author Yamamoto
*/
public class Friend extends Member {

/**
* 年齢を保持
*/
int age;

/**
* 設定
* @param name 年齢
*/
public void set(int age) {
this.age = age;
}

//-----ここから(修正)-----
/**
* 表示
* @param 無し
* @return 無し
*/
@Override
public void disp(){ //オーバーライド
System.out.println("Friendクラスです。");
}
//-----ここまで(修正)-----

}

実行するためのクラス(Execクラス)を作成します。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Exec
package chapter9;

/**
* 実行する為のクラス
* @version 1.0
* @author Yamamoto
*/
public class Exec {

/**
* mainメソッド
*/
public static void main(String[] args) {
//-----ここから(修正)-----
Friend f = new Friend();
f.disp();
//-----ここまで(修正)-----
}
}

コンパイル例:

> javac chapter9¥Member.java
> javac chapter9¥Friend.java
> javac chapter9¥Exec.java

実行例:

> java chapter9.Exec
Friendクラスです。

上記、スーパークラスであるMemberクラスでdisp()メソッドを定義しており、サブクラスであるFriendクラスでオーバーライドしています。サブクラスのオブジェクト内は以下のようになっており、disp()メソッドを実行するとオーバーライドで再定義したオブジェクトが実行されます。

尚、コンパイラーへ明示的に伝える命令のことをアノテーションといいます。アノテーションは@(アットマーク)をつけて指定します。今回、サブクラスであるFriendクラスのdisp()メソッドに「@Override」と記述しています。コンパイラーへこのメソッドはオーバーライドしていることを明示的に伝えています。

オーバーライドのルール

オーバーライドには以下のルールがあります。

  • 引数の構成全く同じでなければならない
    並び順個数などは完全に同じでなければならない

  • 原則として戻り値の型変更しない
    ただし、戻り値が参照型の場合、サブクラス型変えても良い

  • アクセス修飾子は、より公開範囲の広いものにだけ変更して良い

[アクセス修飾子の公開範囲の広い例]

private → デフォルト、protected、public  
デフォルト → protected、public
protected → public

それでは実際に確認してみましょう。

先ほど作成しました、Memberクラスのみを再度修正し、実行結果を確認してください。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Member
package chapter9;
/**
* Memberクラス
* @version 1.0
* @author Yamamoto
*/
public class Member {

/**
* 名前を保持
*/
String name;

/**
* 名前設定
* @param name 名前
*/
public void set(String name) {
this.name = name;
}

/**
* 表示
* @param 無し
* @return 無し
*/
//-----ここから(修正)-----
protected void disp(){ //アクセス修飾子をprotectedへ変更する
//-----ここまで(修正)-----
System.out.println("Memberクラスです。");
}

}

コンパイル例:

> javac chapter9¥Member.java
> javac chapter9¥Friend.java
> javac chapter9¥Exec.java

実行例:

> java chapter9.Exec
Friendクラスです。

オーバーライドの継承元メソッドを実行する方法

オーバーライドによって実行されるメソッドは、継承先で定義しなおしたメソッドです。
もし継承元のメソッド実行したい場合には、superキーワードを利用すると、サブクラス内でのみ実行することができます。

先ほど作成しました、Friendクラスを再度修正し、実行結果を確認してましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Friend
継承 Member
package chapter9;

/**
* Friendクラス
* @version 1.0
* @author Yamamoto
*/
public class Friend extends Member {

/**
* 年齢を保持
*/
int age;

/**
* 設定
* @param name 年齢
*/
public void set(int age) {
this.age = age;
}

/**
* 表示
* @param 無し
* @return 無し
*/
@Override
public void disp(){ //オーバーライド
super.disp(); //スーパークラスのdisp()メソッド実行
System.out.println("Friendクラスです。");
}

}

コンパイル例:

> javac chapter9¥Member.java
> javac chapter9¥Friend.java
> javac chapter9¥Exec.java

実行例:

> java chapter9.Exec
Memberクラスです。
Friendクラスです。

上記、サブクラスのdisp()メソッド内で、「super.disp();」と記載しており、スーパークラスのdisp()メソッドを実行しています。

共変戻り値

オーバーライドでは戻り値を変更することができませんでした。ただし、クラス型参照を返すメソッドだけは、戻り値型サブクラスの型に変更できます。このように、変更した戻り値型を共変戻り値といいます。

例題(共変戻り値)

先ほど作成した、MemberクラスとFriendクラスを戻り値とした、TryクラスとMyTryクラスを作成し、実行結果を確認してみましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Try
package chapter9;
/**
* Tryクラス
* @version 1.0
* @author Yamamoto
*/
public class Try {

/**
* Memberオブジェクト生成
* @param 無し
* @return m Memberオブジェクト
*/
public Member get(){
Member m = new Member();
return m;
}

}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 MyTry
継承 Try
package chapter9;
/**
* MyTryクラス
* @version 1.0
* @author Yamamoto
*/
public class MyTry extends Try {

/**
* Friendオブジェクト生成
* @param 無し
* @return f Friendオブジェクト
*/
public Friend get(){
Friend f = new Friend();
return f;
}

}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 TryExec
package chapter9;
/**
* 実行するためのクラス
* @version 1.0
* @author Yamamoto
*/
public class TryExec {

/**
* main()メソッド
*/
public static void main(String[] args){
MyTry f = new MyTry();
Friend f1 = f.get();
f1.disp();
}

}

コンパイル例:

> javac chapter9¥Try.java
> javac chapter9¥MyTry.java
> javac chapter9¥TryExec.java

実行例:

> java chapter9.TryExec
Memberクラスです。
Friendクラスです。

上記は共変戻り値の例です。スーパークラスであるTryクラスのget()メソッドの戻り値がMember型ですが、それをオーバーライドしたサブクラスである、MyTryクラスのget()メソッドの戻り値がMember型のサブクラスであるFriend型を指定しています。

共変戻り値により、クラスの拡張が容易になり、汎用的に利用できるメリットがあります。

ポリモーフィズム

ポリモーフィズムは、多様性多態性などといわれ、カプセル化、継承に続くオブジェクト指向の三大要素の一つです。
ポリモーフィズムは、オーバーライド参照型の自動型変換によって実現することができます。

スーパークラスへの代入とオーバーライド

以下、EmployeeクラスとSalesMemberクラスを新たに作成し、まずは実行結果を確認しましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Employee
package chapter9;
/**
* 社員クラス
* @version 1.0
* @author Yamamoto
*/
public class Employee {

/**
* 社員名を保持
*/
private String name;

/**
* 社員IDを保持
*/
private int id;

/**
* コンストラクタ
*/
Employee(String name, int id){
this.name = name;
this.id = id;
}

/**
* 社員名取得
* @param 無し
* @return name 社員名
*/
public String getName() {
return name;
}

/**
* 社員名設定
* @param name 社員名
* @return 無し
*/
public void setName(String name) {
this.name = name;
}

/**
* 社員ID取得
* @param 無し
* @return id 社員ID
*/
public int getId() {
return id;
}

/**
* 社員ID設定
* @param id 社員ID
* @return 無し
*/
public void setId(int id) {
this.id = id;
}

/**
* 社員情報表示
* @param 無し
* @return 無し
*/
public void disp(){
System.out.println("-----Employeeクラス-----");
System.out.println("社員名:" + this.name);
System.out.println("社員ID:" + this.id);
}

}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 SalesMember
継承 Employee
package chapter9;
/**
* 社員クラス
* @version 1.0
* @author Yamamoto
*/
public class SalesMember extends Employee {

/**
* 営業部IDを保持
*/
private int salesId;

/**
* コンストラクタ
*/
SalesMember(String name , int id, int salesId){
super(name,id);
this.salesId = salesId;
}

/**
* 営業部ID取得
* @param 無し
* @return salesId 営業部ID
*/
public int getSalesId() {
return salesId;
}

/**
* 営業部ID設定
* @param salesId 営業部ID
* @return 無し
*/
public void setSalesId(int salesId) {
this.salesId = salesId;
}

/**
* 営業部情報表示
* @param 無し
* @return 無し
*/
@Override
public void disp(){
System.out.println("-----SalesMemberクラス-----");
System.out.println("営業部ID:" + this.salesId);
}
}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 EmployeeExec
package chapter9;
/**
* 実行する為のクラス
* @version 1.0
* @author Yamamoto
*/
public class EmployeeExec {
/**
* main()メソッド
*/
public static void main(String[] args) {
Employee emp = new SalesMember("山田",123,1000);
emp.disp();
}
}

コンパイル例:

> javac chapter9¥Employee.java
> javac chapter9¥SalesMember.java
> javac chapter9¥EmployeeExec.java

実行例:

> java chapter9.EmployeeExec
-----SalesMemberクラス-----
営業部ID:1000

上記の場合、Employeeクラスがスーパークラス、SalesMemberクラスがサブクラスです。EmployeeExecクラスが実行するクラスです。

EmployeeExecクラスに定義されている
「Employee emp = new SalesMember(“山田”,123,1000);」に注目してください。

左辺では「new SalesMember(“山田”,123,1);」でSalesMemberクラスのインスタンス化を行っています。

右辺ではEmployeeクラス型の変数「emp」へ代入しています。つまり、サブクラスであるSalesMemberクラスのオブジェクトをスーパークラスの型であるEmployee型へ、自動型変換によって代入しています。

そして、Employeeクラス型の変数「emp」の「disp()」メソッドを実行しています。

出力結果を見るとわかるように、emp.disp(); を実行すると、SalesMemberクラスでオーバーライドしたメソッドである disp()メソッドが実行されています。

このように、emp変数の型には関係なく、代入された時のオブジェクトでオーバーライドしたメソッドが実行されていることがわかります。

これを ポリモーフィズムといいます。

ポリモーフィズムは、同じ型の変数でも、違う型のオブジェクトを代入することで、様々な機能に変身することができます。

ポリモーフィズムの利用

機能を変更する継承を使うと、スーパークラス大まかな機能を作っておき、サブクラス詳細を作りこませてシステムを完成させることができます。この方法では、サブクラスでのオーバーライドの違いによって、同じスーパークラスからいろいろなシステムを作成できます。

そのようなシステムを「アプリケーション」と呼ぶことにすると、同じスーパークラスから機能の違う、いくつものアプリケーションを作成することができます。

一方、スーパークラス型の変数に、いろいろなサブクラス型のオブジェクトを代入して実行するとポリモーフィズムが働くので、それぞれ違う機能を発揮します。したがって、アプリケーションスーパークラス型の変数に、いろいろなアプリケーション代入して実行すると、それぞれ独自のアプリケーションとして動くはずです。

以下は、色々な言語であいさつをする「他言語挨拶システム」です。

項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Greeting
package chapter9;
/**
* 挨拶文を作成するクラス
* @version 1.0
* @author Yamamoto
*/
public class Greeting {

/**
* 言語名を取得
* @param 無し
* @return null
*/
public String language(){
return null;
}

/**
* 挨拶文を取得
* @param 無し
* @return null
*/
public String message(){
return null;
}
}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 JapaneseGreeting
継承 Greeting
package chapter9;
/**
* 日本語で挨拶文を作成するクラス
* @version 1.0
* @author Yamamoto
*/
public class JapaneseGreeting extends Greeting {

/**
* 日本語を取得
* @param 無し
* @return "Japanese"
*/
public String language(){
return "Japanese";
}

/**
* 日本語の挨拶文を取得
* @param 無し
* @return "こんにちは"
*/
public String message(){
return "こんにちは";
}
}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 Talker
package chapter9;
/**
* 挨拶を実行するクラス
* @version 1.0
* @author Yamamoto
*/
public class Talker {

/**
* 挨拶情報を表示
* @param gr Greeting型
* @return 無し
*/
public void run(Greeting gr){
//言語を表示する
System.out.println(gr.language());
//挨拶を表示する
System.out.println(gr.message());
}
}
項目 名前
プロジェクト alj_study
パッケージ chapter9
クラス名 LangurageExec
package chapter9;
/**
* 実行する為のクラス
* @version 1.0
* @author Yamamoto
*/
public class LangurageExec {

/**
* main()メソッド
*/
public static void main(String[] args) {
Talker talker = new Talker();
talker.run(new JapaneseGreeting());
}

}

コンパイル例:

> javac chapter9¥Greeting.java
> javac chapter9¥JapaneseGreeting.java
> javac chapter9¥Talker.java
> javac chapter9¥LangurageExec.java

実行例:

> java chapter9.LangurageExec
Japanese
こんにちは

Greetingクラスは挨拶文を作るクラス(スーパークラス)です。language()メソッドとmessage()メソッドが定義されているのみで、単にnullを返すだけのメソッドです。このままでは何の役に立たないメソッドです。
その為、継承したら機能を変更しなくてはならないメソッドです。

JapaneseGreetingクラスは日本語の挨拶を行うクラスです(Greetingクラスのサブクラスです。)

次はTalkerクラスです。Talkerクラスではポリモーフィズムを利用しています。
Talkerクラスは挨拶を実行するクラスです。runメソッドだけがあります。runメソッドは、Greeting型の引数を受け取り、引数から言語と挨拶文を取り出して表示します。

最後のLangurageExecクラスは実行するクラスです。実行結果を見ると、日本語での挨拶ができていることがわかります。

余力がある人は、JapaneseGreetingクラス以外に、英語であいさつする「EnglishGreetingクラス」を作成し、以下のような実行結果になるようにしてみてください。

実行例:

> java chapter9.LangurageExec
English
Hello

まとめ

以下の要点をしっかり理解してから次の章へ進んでください。

継承ツリー上のオーバーロード

  • 同じ継承ツリーであれば、サブクラススーパークラスのメソッドオーバーロードできる
  • クラス内でのオーバーロードでは、引数構成を変えないとコンパイルエラーになる
  • 継承ツリー上クラス間オーバーロードでは、引数構成を変えないオーバーライドになる

継承ツリー上のオーバーロードの規則

  • 引数の構成(型、順序、数)を変えることでオーバーロードになる
  • 戻り値型アクセス修飾子は自由に変えることができる
  • 引数の構成(型、順序、数)同じだとコンパイルエラーになる

メソッドのオーバーライド

  • オーバーライドとはスーパークラスで定義したメソッドを再度サブクラスで定義しなおすこと
  • 継承先のメソッドで、メソッド名が同じでかつ引数構成も全く同じメソッドを定義し、内容を再度定義すると、継承先のメソッドが有効になり、継承元のメソッドは無効になる。
  • サブクラスのオブジェクトをインスタンス化し、サブクラス型の変数へ代入し、オーバーライドしたメンバメソッドを実行すると、継承先で再定義したメソッドが実行される

オーバーライドの規則

  • 引数の構成が全く同じでなければならない
  • 戻り値は同じでなければならない
  • アクセス修飾子はよりゆるいレベルにのみ変更できる
  • 継承したメソッドだけをオーバーライドできる
  • superキーワードを利用するとスーパークラスのメソッドを実行することができる

共変戻り値

  • オーバーロードの戻り値は、原則同じでなければならないが、継承ツリー関係がある場合はサブクラスの型を戻り値に指定することも可能(共変戻り値)
  • 共変戻り値を定義すると、クラスの拡張が容易になり、汎用的に利用できる

ポリモーフィズム

  • サブクラスのオブジェクトをスーパークラス型の変数へ代入し、オーバーライド元のメソッドを実行するとオーバーライド先のメソッドが実行される(ポリモーフィズム)
  • ポリモーフィズムは、オーバーライド + 参照型の自動型変換をおこなうと利用できる。
  • ポリモーフィズムを利用すると様々な機能を実現することができる