支援対象地域:札幌、仙台、関東、愛知、関西、広島、福岡

  • TOP
  •   
  • コラム
  •   
  • Java オブジェクト指向3(多態性

はじめに

Javaのオブジェクト指向を実現する上で重要となる3つの機能(カプセル化、継承、多態性)について3回にわたってご紹介します。 今回ご紹介する機能は、多態性です。


多態性(1)

抽象クラス

多態性のイメージは、「あるものをザックリ捉えて扱おう」というものです。

例えば、レンタカーを想像してみてください。
運転する車は、初めて乗る車なのに、多くのドライバーは何も問題なく運転できるでしょう。その理由は、「車種は違えど、ザックリ捉えれば車」だからです。

このように、ザックリ捉えることで、厳密には違うものを「同じようなもの」として利用する方法をプログラムで実現する機能が「多態性(ポリモフィズム)」ということです。

多態性を考えるうえで欠かせない、2つのクラスをご紹介します。
それは、「抽象クラス」と「インターフェース」です。
これらは、厳密にいえば、「継承」の内容ですが、ザックリ捉えれば、「オブジェクト指向」と言えます。

【抽象クラス】

抽象クラスとは、詳細未定のメソッド(抽象メソッド)を含むクラスです。
アクセス修飾子 abstract class クラス名{
 //フィールド

 //抽象メソッド
 アクセス修飾子 abstract 戻り値 メソッド名(引数リスト) ;
}

《抽象メソッド・抽象クラスの注意点》

1.抽象メソッド及び抽象クラスは、必ずabstractを付ける
全ての抽象メソッドの処理が確定して初めてabstractが外すことが許され、通常クラスになります。
また、抽象クラス内全てのメソッドが、抽象メソッドである必要はありません。

2.抽象クラスは、newインスタンス化禁止
 継承専用クラスは、抽象クラスとして宣言します。

3.抽象メソッドは、「波カッコ{}」ではなく、「セミコロン;」を付ける
 メソッドの処理が未定であることを明示するためです。

4.抽象メソッドのオーバーライド必須
抽象クラスを継承したサブクラスは、必ず抽象メソッドをオーバーライドします。しかし、全ての抽象メソッドをオーバライドする必要はありません。



【インターフェース】

インターフェースとは、次の条件を満たす特別な抽象クラスです。
また、インターフェース内で宣言されたメソッドは、自動的に「public かつ abstract」となるため、「アクセス修飾子 abstract」は省略しても構いません。
《インターフェースの条件》
  1. 全てのメソッドが抽象メソッドである
  2. 基本的にフィールドを1つも持たない
//インターフェース
アクセス修飾子 interface インターフェース名{
 //抽象メソッド
 戻り値 メソッド名(引数リスト) ;
}

《インターフェースの注意点》

1.インターフェース定義は、interfaceを付ける

2.インターフェース内のフィールドは全て定数になる
インターフェース内でフィールドを宣言すると、自動的に「public static final」が補われ、定数になります。


3.インターフェースの継承は、「extends」ではなく、「implements」を用いる
インターフェースを継承したサブクラスは、必ず抽象メソッドをオーバーライドします。しかし、全ての抽象メソッドをオーバライドする必要はありません。
その場合は、インターフェースを継承したサブクラスは、抽象クラスになります。
【implements vs. extends】
継承元
(スーパークラス)
継承先
(サブクラス)
使用
キーワード
継承可能数
クラス クラス extends 1つのみ
インターフェース インターフェース extends 1つ以上
インターフェース クラス implements 1つ以上

同種(クラス同士、インターフェース同士)の継承は「extends」、
異種なら「implements」を使用します。


4.複数のインターフェースをスーパークラスとして継承できる(多重継承OK)
インターフェースは、メソッド内容を一切定めておらず、処理内容が衝突することがないため可能です。


多態性(2)

1)多態性の利用

多態性には、カプセル化(「private」、「public」)や継承(「extends」)のような専用の文法はありません。 「代入」を用いることで、多態性を使えます。
箱の型 変数名 = new 中身の型;」としてインスタンス化します。
抽象クラスやインターフェースを「中身の型」とすることはできませんが、「箱の型」として利用することは可能です。

public class Main {
 public static void main(String[] args) {
  Animal a1 = new Dog("犬");
  Animal a2 = new Cat("ネコ");
  a1.crying();
  a2.crying();
 }
}


//Animalクラス(抽象クラス)
abstract class Animal{
 public String type;

 public Animal(String type) { this.type = type; }

 public abstract void crying();
}

//Dogクラス(Animalクラスを継承)
class Dog extends Animal{
 public Dog(String type) { super(type); }

 public void crying() { System.out.println(super.type +"→ワン"); }
}

class Cat extends Animal{
 public Cat(String type) { super(type); }

//Catクラス(Animalクラスを継承)
 public void crying() { System.out.println(super.type + "→ニャー"); }
}
実行結果
犬→ワン
ネコ→ニャー

Mainクラス内の「Animal a1 = new Dog("犬");」、「Animal a2 = new Cat("ネコ");」は、 変数(a1、a2)という名前箱(Animal)実態(Dog、Cat)が入っているということです。

実際に動くメソッドの中身は、実態によって決まるため、それがどんな型に入っているかは関係ありません。

箱の型:どのメソッドを「呼べるか」を決める

中身の型:メソッドが呼ばれたら、「どう動くか」を決める



2)捉え方の変更

「箱」にあるメソッドは呼び出せますが、「実態」のみにあるメソッドは呼び出せません。
では、「実態」のみにあるメソッドを呼び出したい場合は、どのようにすればよいのでしょうか?

それは、「箱」を変えて、捉え直すことです。
「キャスト演算子」を用いることで、「箱」を変更することができます。

「Animal型(変数a1) → Dog型(変数d)」に変更
  Dog d = (Dog) a1;

「Animal型(変数a2) → Cat型(変数c)」に変更
  Cat c = (Cat) a2;

しかし、このキャストには大きな問題点があります。
それは、「Dogの箱」に「Catの実態」が入ってしまう可能性があるということです。
このままだと、「Catの実態」を「Dogの箱」に無理やり入れようとしているためエラーが発生してしまいます

このエラーを回避するために、「箱」を変える前に
instanceof演算子」を用いて「箱」を変えても大丈夫かチェックします。

変数 instanceof 型名

※「箱」を変えて大丈夫なら、trueが返ります。

実態(Dogクラス、Catクラス)のみにあるメソッドを、「箱」を変えて呼び出すサンプルコードが次のようになります。

public class Main {
 public static void main(String[] args) {
  Animal a1 = new Dog("犬");
  Animal a2 = new Cat("ネコ");

  a1.crying();
  a2.crying();

  if(a1 instanceof Dog) {
  Dog d = (Dog)a1;
  d.likeThings();
  }

  if(a2 instanceof Cat) {
  Cat c = (Cat)a2;
  c.likeFood();
  }

 }
}


//Animalクラス(抽象クラス)
abstract class Animal{
 public String type;

 public Animal(String type) { this.type = type; }

 public abstract void crying();
}

//Dogクラス(Animalクラスを継承)
class Dog extends Animal{
 public Dog(String type) { super(type); }

 public void crying() { System.out.println(super.type +"→ワン"); }
 public void likeThings() { System.out.println(super.type +"→ボール遊びが好き"); }
}

class Cat extends Animal{
 public Cat(String type) { super(type); }

//Catクラス(Animalクラスを継承)
 public void crying() { System.out.println(super.type + "→ニャー"); }
 public void likeFood() { System.out.println(super.type +"→魚が好き"); }
}
実行結果
犬→ワン
ネコ→ニャー
犬→ボール遊びが好きです
ネコ→魚が好き

まとめ

多態性」についてご紹介しました。 お役に立てると幸いです。