ポリモーフィズム

「ポリモーフィズム」とは日本語で「多様性」を意味します。
オブジェクト指向言語における多様性とは、継承関係から構築されるクラス階層から、
クラスを操作する際に親クラスの型で操作したり、子クラスの型を直接指定して操作したりすることがその1つです。

オーバロードを利用して、引数違いのメソッドの同一名を利用することも多様性の1つです。

オーバライドを利用して、親メソッドの抽象メソッドまたは実体メソッドを、子クラスがオーバライドすることで
親メソッドの機能を追加または変更することが可能です。これも多様性の1つです。

ポリモーフィズムのサンプル

AbstractSamples.java
//ライセンスコメント割愛
package jp.co.yourcompany.education.samples;

import java.util.ArrayList;
import java.util.List;

/**
 * 多様性の1つであるクラス階層の親クラスを指定して操作するサンプルクラス
 * @author raita.kuwabara
 */
public class PolymorphismSample extends AbstractSamples{

    /**
     * サンプルを実行する。
     */
    @Override
    protected void execAllSamples() {
        polymorphismNumber();
    }

    private void polymorphismNumber(){
        outputStartMethod( "polymorphismNumber");

        //ここも多様性を利用しています。 List <- ArrayList
        List<Number> numberList = new ArrayList<Number>();

        Byte numberByte = new Byte("100");
        Short numberShort = new Short("101");
        Integer numberIntger = new Integer("102");
        Long numberLong = new Long("103");
        Float numberFloat = new Float("104.01");
        Double numberDobule = new Double("105.0001");

        numberList.add( numberByte );
        numberList.add( numberShort );
        numberList.add( numberIntger );
        numberList.add( numberLong );
        numberList.add( numberFloat );
        numberList.add( numberDobule );

        for( Number number : numberList ){
            System.out.println("number.getClass().getName():" +  number.getClass().getName() );
            //toStringはjava.lang.Objectメソッドに定義されて子クラスでオーバライドされている。
            System.out.println("number.toString:" +  number.toString() );
            //intValueはjava.lang.Numberメソッドで定義されて各ラッパークラスでオーバライドされている。
            System.out.println("number.intValue:" +  number.intValue() );
            System.out.println("------------------------");
        }
        outputEndMethod( "polymorphismNumber");
    }

    /**
     * javaコマンドから実行されるメインメソッド
     * @param args コマンド引数不要
     */
    public static final void main( String[] args){

        PolymorphismSample sample = new PolymorphismSample();
        sample.execAllSamples();
    }

}
				
出力結果
----- polymorphismNumber START -----
number.getClass().getName():java.lang.Byte
number.toString:100
number.intValue:100
------------------------
number.getClass().getName():java.lang.Short
number.toString:101
number.intValue:101
------------------------
number.getClass().getName():java.lang.Integer
number.toString:102
number.intValue:102
------------------------
number.getClass().getName():java.lang.Long
number.toString:103
number.intValue:103
------------------------
number.getClass().getName():java.lang.Float
number.toString:104.01
number.intValue:104
------------------------
number.getClass().getName():java.lang.Double
number.toString:105.0001
number.intValue:105
------------------------
----- polymorphismNumber END -----
					

1つ1つ丁寧に多様性が利用されている行を検証していきましょう。

AbstractSamples.javaより
		List<Number> numberList = new ArrayList<Number>();
				

この行で既に2つの多様性が利用されています。
1つに、ArrayListの変数numberListの型に直接ArrayList指定せずに親クラスのListを指定しています。

ArrayListはリスト型オブジェクトで多く利用されますが、戻り値や引数の定義でArrayListを直接指定しません。
これは、コーディング規約に掲載した電通国際情報サービス様のコーディング規約にも定義されています。

理由は、引数、戻り値を最下層の子クラスで指定するとその型でしかメソッドが利用できずに多様性が活かせないからです。
上位クラスのListで定義しておけば、Listを継承した他のクラス例えばLinkedListなども対象になります。

次に<Number>はリスト型の講義で詳しく説明しますがListオブジェクトに格納するクラスを明示しています。
これがIntegerを直接指定していない事により、Numberクラスを継承した子クラス全てをaddメソッドでリストに格納できます。

AbstractSamples.javaより
    for( Number number : numberList ){
        System.out.println("number.getClass().getName():" +  number.getClass().getName() );
        //toStringはjava.lang.Objectメソッドに定義されて子クラスでオーバライドされている。
        System.out.println("number.toString:" +  number.toString() );
        //intValueはjava.lang.Numberメソッドで定義されて各ラッパークラスでオーバライドされている。
        System.out.println("number.intValue:" +  number.intValue() );
        System.out.println("------------------------");
    }
				

インスタンス.getClass().getName()はLog出力などで良く利用されます。
インスタンスのクラスの完全修飾子名の文字列を返します。

出力結果を見るとそれぞれのラッパークラス名(Integerなど)が出力されました。
Number型でnumber変数を定義していても実行されているのはadd時に設定した子クラスであることが分かります。

JavaAPI Numberクラスをご覧ください。

toStringメソッドは、java.lang.Objectで定義されて各クラスでオーバーライドされている事がAPIからわかります。
更にintValue()メソッドはNumberクラスの抽象メソッドなので子クラス(Integerなど)で必ず実装されている必要があります。

そして、呼び出しはNumber型の変数ですが実際に呼び出されているのは各ラッパークラスのintValue()になります。

このように最下層の子クラスを直接変数の型として利用しないで、親クラスを指定することで
親の抽象メソッドを実行する事で親を継承した全ての子クラスに対して実行できます。

このようにメンバーフィールド、メソッドの戻り値、引数、メソッド内の変数はできるだけ直接クラスを指定するのではなく、
インターフェイスや親クラスを指定するのがオブジェクト指向言語の多様性を活かした作法になります。

オーバーロード

オーバーロードとは、クラスのコンストラクタ、メソッドに引数違いの同名のコンストラクタ、メソッドを定義することを意味します。

オーバーロードは、1つのコンストラクタ及び1つのメソッドに対して、原則同じ主目的の機能を実装します。
違いは引数の渡し方により、メソッド内で主目的を実現する前の前処理が異なるだけです。

このオーバーロードにより、コンストラクタ及びメソッドの呼び方に幅が広がります。これも多様性の1つです。

オーバーロードのサンプル

OverloadSample.javaより
//コメント全て割愛
package jp.co.yourcompany.education.samples;

public class OverloadSample  extends AbstractSamples {

    public static final int DEFAULT_TAX = 8;
    protected int tax;
    protected int price;
    protected int quantity;

    public OverloadSample(){
    }

    public OverloadSample( int price , int quntity){
        setPrice( price );
        setQuantity( quntity );
    }

    public OverloadSample( int price , int quntity , int tax){
        this( price , quntity );
        setTax( tax );
    }

    public int consumptionTax(){
        return ( price * quantity * tax) / 100;
    }

    public int consumptionTax( int price , int quntity , int tax){
        setPrice( price );
        setQuantity( quntity ) ;
        setTax( tax ) ;
        return this.consumptionTax();
    }

    public int consumptionTax( int price , int quntity){
        return this.consumptionTax( price , quntity , OverloadSample.DEFAULT_TAX );
    }

    @Override
    protected void execAllSamples() {
        System.out.println("呼び出し方法の違いのサンプルなのでmainメソッドで確認して下さい。");
    }

    public int getTax() {
        return tax;
    }

    public void setTax(int tax) {
        this.tax = tax;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public static final void main( String[] args){
        //呼び出し方1
        System.out.println("OverloadSample() サンプル1");
        OverloadSample sample = new OverloadSample();
        sample.setPrice( 10000 );
        sample.setQuantity( 1 );
        sample.setTax( OverloadSample.DEFAULT_TAX );
        System.out.println("consumptionTax:" + sample.consumptionTax());

        //呼び出し方2
        System.out.println("OverloadSample() サンプル2");
        //デフォルトの消費税8%が適用
        OverloadSample sample2 = new OverloadSample( 10000 , 1 );
        System.out.println("consumptionTax:" + sample2.consumptionTax());

        //呼び出し方3 税率が変更されたときの呼び出し 消費税10%指定
        System.out.println("OverloadSample() サンプル3");
        OverloadSample sample3 = new OverloadSample( 10000 , 1 , 10);
        System.out.println("consumptionTax:" + sample3.consumptionTax());

        //呼び出し方4 消費税10%指定
        System.out.println("OverloadSample() サンプル4");
        OverloadSample sample4 = new OverloadSample();
        System.out.println("consumptionTax:" + sample4.consumptionTax(498 , 1 , 10));

        //呼び出し方5 デフォルト消費税8%適用
        System.out.println("OverloadSample() サンプル5");
        OverloadSample sample5 = new OverloadSample();
        System.out.println("consumptionTax:" + sample5.consumptionTax(498 , 1 ));
    }
}
			
実行結果
OverloadSample() サンプル1
consumptionTax:800
OverloadSample() サンプル2
consumptionTax:0
OverloadSample() サンプル3
consumptionTax:1000
OverloadSample() サンプル4
consumptionTax:49
OverloadSample() サンプル5
consumptionTax:39
				

コンストラクタとメソッドでそれぞれ、オーバーロードを利用しています。
このようにオーバロードを利用すると引数違いの主目的の処理(サンプルでは、消費税額の計算)を同じ事が実現できます。

オーバロードはとても便利でフレームワークでは多用されます。実際のビジネスシステムで多様すると上記例のように
処理の呼び方が増えて、開発者がどのような呼び方で呼べばよいのか混乱し易いデメリットもあります。

チームで開発するときには、共通チームなどに配属してる上級エンジニアが全体のオーバライドの指針について、
コーディング規約で明記して展開した方が良いでしょう。

オーバーライド

オーバーライドとは、継承した親クラスの抽象メソッドの実体化又は親クラスのメソッドの機能の上書きを意味します。

前頁で説明した@Override アノテーションで明示します。

オーバライドで親メソッドを変更する時に重要な点は、
メソッドの意味する主目的の機能から類似、拡張、派生している機能であることです。

オーバーライドのサンプル

OverrideSample.javaより
package jp.co.yourcompany.education.samples;

public class OverrideSample  extends AbstractSamples {

    /**
     * オーバライドの例(親メソッドの機能の上書き)
     */
    @Override
    public final void outputStartMethod( String methodName ){
        System.out.println( "サンプルメソッド" + methodName + "を開始します。");
    }

    /**
     * 抽象クラスのオーバライドの例
     */
    @Override
    protected void execAllSamples() {
        outputStartMethod( "execAllSamples" );
        System.out.println(" 開始メッセージが日本語になりました。");
        outputEndMethod( "execAllSamples" );
    }

    /**
     * Java コマンドから実行するmainメソッド
     */
    public static final void main( String[] args){
        OverrideSample sample = new OverrideSample();
        sample.execAllSamples();
    }
}
				
実行結果
サンプルメソッドexecAllSamplesを開始します。
 開始メッセージが日本語になりました。
----- execAllSamples END -----
				

親メソッドで定義されたoutputMethodがオーバライドされて日本語になりました。
ただし、メソッドの意味する実行するメソッド名の開始を出力する目的に相違はありません。

親メソッドに対してオーバライドして子メソッドで派生させる場合には次のように記述するパターンが多いです。

オーバライドの良く見かけるケース
    @Override
    protected void hoge( String message){
        super.hoge( message );
        doSomething();
    }
	

このようにhogeメソッドは子クラスでオーバライドしているが、super.hoge( message )を呼び出し、
差分機能の doSomething()メソッドを拡張したと理解できます。

このようにオーバライドしたときにどのような機能拡張、派生をされたかが明確に分かるメソッドが優れた記述といえます。

全てのサンプルのダウンロードと実行方法

  • java_sample12.zipファイルを「c:¥download¥java¥samples¥」に保存して下さい。
  • java_sample12.zipファイルを「c:¥projects」配下に展開して下さい。
  • コマンドプロンプトを起動して、「sample12jar.bat」を実行して下さい。
  • javac,jar,実行されてdeployディレクトリに「education-main.jar」が生成されます。
  • コマンドプロンプトから「sample12a.bat」を実行して下さい。PolymorphismSampleクラスが実行されます。
  • コマンドプロンプトから「sample12b.bat」を実行して下さい。Overloadクラスが実行されます。
  • コマンドプロンプトから「sample12c.bat」を実行して下さい。Overrideクラスが実行されます。
  • ソースファイルを改修して実行する場合には、毎回「sample12jar.bat」を実行して下さい。
  • Linux,Unix,iOSの方はバッチファイルはお手数ですが作成して下さい。改行コードが「CRLF」の点ご留意ください。