BLOG
ブログ
  • TOP
  • BLOG
  • 【Javaシルバー対策】ポリモーフィズム

【Javaシルバー対策】ポリモーフィズム

【Javaシルバー対策】ポリモーフィズム

CONTENT

こんにちは、東京エンジニアSWです。

今回はポリモーフィズムについて解説していきます。前回の記事は以下です。あわせてご確認下さい。

■【Java Silver対策】クラスの継承について
 【Java Silver対策】クラスの継承

ポリモーフィズム自体はJavaブロンズで出てくる要素となっていますが、シルバーではそこから一歩踏み込んだ考え方や扱い方についての細かな知識が問われる内容となっています。


■Javaにおけるポリモーフィズムとは

ポリモーフィズムとは直訳すると多態性(たたいせい)と言います。Javaの世界では異なる性質を持つクラスに対して同一の操作で扱うことができる仕組みがあり、これをポリモーフィズムと呼びます。

つまり、共通の操作方法を提供することにより、呼び出し側がその違いを意識することなく操作できるのです。身近な例で例えると、DVD/CDプレイヤーは、ディスクというオブジェクトを入れて再生ボタンを押すと、DVDだったら動画が再生され、CDだったら音楽が再生される、ということです。



■スーパークラスから継承する例

まずは最も単純な例として、スーパークラスからメソッドをオーバーライドすることでポリモーフィズムによるDVD/CDプレイヤーを再現してみます。

public class Test {
       public static void main(String[] args) {
          Player p = new CDPlayer();
          p.play();
     }
}


class Player {
    protected void play() {}
}

class DVDPlayer extends Player {
       public void play() {
          System.out.println("Play DVD.");
   }
}

class CDPlayer extends Player {
     public void play() {
         System.out.println("Play CD.");
       }
}

この例ではPlayerクラスをDVDPlayer、CDPlayerがそれぞれ継承していて、さらにPlayerクラスのplayメソッドをオーバーライドしています。この例のように「親クラスの型の変数で子クラスのインスタンスを扱えること」がポリモーフィズムの本質です。

コード自体は実行可能で、結果は「Play CD.」と出力されますが、このコードにはいろいろと問題があります。

1番の問題としてはPlayer自体がインスタンス化できることで、Playerのplayメソッドが実行できてしまいますが「具体的な処理を持たないのでなにも出力されない」といったところです。

これを解決するために抽象クラスやインターフェイスを使用します。


■インターフェイスを使用する例

先ほどのコードから、Playerをインターフェイスに、DVD/CDクラスはそれぞれインターフェイスを実装するように変更しました。

public class Test {
      public static void main(String[] args) {
          Player p = new CDPlayer();
          p.play();
      }
}


interface Player {
      void play();
}


class DVDPlayer implements Player {
     public void play() {
         System.out.println("Play DVD.");
      }
}


class CDPlayer implements Player {
    public void play() {
          System.out.println("Play CD.");
     }
}

こちらも実行結果は「Play CD.」と出力されます。Playerインターフェイスは、それ自体では実行できないので、安全なコード設計と言えます。

次に共通処理を定義したいケースがあります。インターフェイスには処理を記述することはできないので、抽象クラスを使用します。
補足:厳密にいえばJava8以降はdefaultメソッドで処理を記述することも可能(これはまた次の機会に)


■抽象クラスを使用する例

以下のコードは最初のコードから以下の点を変更しました。

1. Playerクラスを抽象クラスに変更
2. Playerクラスにメンバー変数(vol)とボリューム設定メソッド(setVol)を追加
3. playメソッド呼び出し前にボリューム設定するように処理を追加
4. playメソッド呼び出し時にボリューム設定が分かるように表示

public class Test {
       public static void main(String[] args) {
           Player p = new CDPlayer();
        p.setVol(5);
         p.play();
  }
}


abstract class Player {
      protected int vol;
      abstract void play();
      public void setVol(int vol) {
           this.vol = vol;
      }
}


class DVDPlayer extends Player {
      public void play() {
          System.out.println("Play DVD. (vol=" + this.vol + ")");
       }
}


class CDPlayer extends Player {
     public void play() {
         System.out.println("Play CD. (vol=" + this.vol + ")");
     }
}

これを実行すると「Play CD. (vol=5)」と出力されます。抽象クラスは具象メソッド(実体のある処理)を記述できるので、共通処理をこの例のように定義することが可能です。

ここで注意しなくてはいけないのは、「抽象クラスには具象メソッドを定義できるが、具象クラスに抽象メソッドを定義することはできない」ということです。

例えば上記のコードの abstract class Player を class Playerとして以下のように定義するとコンパイルエラーになります。

class Player {
     protected int vol;
     abstract void play();
     public void setVol(int vol) {
         this.vol = vol;
     }
}

これを許すと「Playerクラスの中身の無いplayメソッドを呼び出す」という矛盾が発生してしまうためです。Silver試験でもひっかけ問題としてよく出るので、しっかりと理解しておく必要があります。


■まとめ
ポリモーフィズムを正しく理解するためには、インターフェイスと抽象クラスの違いを明確にしておくことが重要です。両者はどちらも「共通の型」として利用できますが、目的と使いどころが異なります。


◆インターフェイス
・「できること(役割)」を定義する
・メソッドは基本的に抽象メソッド(※Java8以降はdefaultメソッド可)
・インスタンス変数は持てない(定数のみ)
・多重実装が可能
・クラス階層に縛られない設計ができる

◆抽象クラス
・「共通の土台」を作る
・抽象メソッドと具象メソッドの両方を定義できる
・インスタンス変数を持てる
・コンストラクタを持てる
・単一継承のみ


■試験対策として特に重要なのは次のポイントです。
・抽象メソッドを持つクラスは必ずabstractでなければならない
・インターフェイスのメソッドは暗黙的にpublic abstract
・実装クラスではpublicでオーバーライドする必要がある
・親クラスの型の変数で子クラスのインスタンスを扱えることがポリモーフィズムの本質

設計の観点で言えば、「共通処理を持たせたいなら抽象クラス」「役割を定義したいならインターフェイス」と覚えておくと判断しやすくなります。

Javaシルバーでは、これらの違いを正確に理解しているかどうかが問われます。単なる暗記ではなく、「なぜそうなるのか」を意識して整理しておきましょう。