Java オブジェクト指向3(多態性)
はじめに
Javaのオブジェクト指向を実現する上で重要となる3つの機能(カプセル化、継承、多態性)について3回にわたってご紹介します。 今回ご紹介する機能は、多態性です。
多態性(1)
抽象クラス
多態性のイメージは、「あるものをザックリ捉えて扱おう」というものです。
例えば、レンタカーを想像してみてください。
運転する車は、初めて乗る車なのに、多くのドライバーは何も問題なく運転できるでしょう。その理由は、「車種は違えど、ザックリ捉えれば車」だからです。
このように、ザックリ捉えることで、厳密には違うものを「同じようなもの」として利用する方法をプログラムで実現する機能が「多態性(ポリモフィズム)」ということです。
多態性を考えるうえで欠かせない、2つのクラスをご紹介します。
それは、「抽象クラス」と「インターフェース」です。
これらは、厳密にいえば、「継承」の内容ですが、ザックリ捉えれば、「オブジェクト指向」と言えます。
【抽象クラス】
抽象クラスとは、詳細未定のメソッド(抽象メソッド)を含むクラスです。//フィールド
//抽象メソッド
アクセス修飾子 abstract 戻り値 メソッド名(引数リスト) ;
}
《抽象メソッド・抽象クラスの注意点》
また、抽象クラス内全てのメソッドが、抽象メソッドである必要はありません。
2.抽象クラスは、newインスタンス化禁止
継承専用クラスは、抽象クラスとして宣言します。
3.抽象メソッドは、「波カッコ{}」ではなく、「セミコロン;」を付ける
メソッドの処理が未定であることを明示するためです。
4.抽象メソッドのオーバーライド必須
【インターフェース】
インターフェースとは、次の条件を満たす特別な抽象クラスです。また、インターフェース内で宣言されたメソッドは、自動的に「public かつ abstract」となるため、「アクセス修飾子 abstract」は省略しても構いません。
- 全てのメソッドが抽象メソッドである
- 基本的にフィールドを1つも持たない
アクセス修飾子 interface インターフェース名{
//抽象メソッド
戻り値 メソッド名(引数リスト) ;
}
《インターフェースの注意点》
2.インターフェース内のフィールドは全て定数になる
3.インターフェースの継承は、「extends」ではなく、「implements」を用いる
その場合は、インターフェースを継承したサブクラスは、抽象クラスになります。
継承元 (スーパークラス) |
継承先 (サブクラス) |
使用 キーワード |
継承可能数 |
クラス | クラス | extends | 1つのみ |
インターフェース | インターフェース | extends | 1つ以上 |
インターフェース | クラス | implements | 1つ以上 |
同種(クラス同士、インターフェース同士)の継承は「extends」、
異種なら「implements」を使用します。
4.複数のインターフェースをスーパークラスとして継承できる(多重継承OK)
多態性(2)
1)多態性の利用
多態性には、カプセル化(「private」、「public」)や継承(「extends」)のような専用の文法はありません。
「代入」を用いることで、多態性を使えます。
「箱の型 変数名 = new 中身の型;」としてインスタンス化します。
抽象クラスやインターフェースを「中身の型」とすることはできませんが、「箱の型」として利用することは可能です。
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演算子」を用いて「箱」を変えても大丈夫かチェックします。
※「箱」を変えて大丈夫なら、trueが返ります。
実態(Dogクラス、Catクラス)のみにあるメソッドを、「箱」を変えて呼び出すサンプルコードが次のようになります。
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 +"→魚が好き"); }
}
ネコ→ニャー
犬→ボール遊びが好きです
ネコ→魚が好き
まとめ
「多態性」についてご紹介しました。 お役に立てると幸いです。