公開日:7/28/2020 更新日:1/5/2025
こんにちは。Java を実務で使用しだして半年の新人エンジニアです。
私は新人研修期間に Java の基本構文やオブジェクト指向について一通り学びました。その際、オブジェクト指向の多態性が実務で何の役に立つのか、あまりピンとこなかった記憶があります。仕組みは分かるが、実際にどう使うの?みたいな。そんな私も実務を通し、多態性の素晴らしさに少し気付けたので、多態性 (ポリモーフィズム) の使用シーンと使用目的を自分視点で分かりやすく説明できたらなと思います。
JAVAのオブジェクト指向、特に多態性 (ポリモーフィズム) の理解があいまいな人
多態性について下記サイトが分かりやすく説明していたので引用すると、以下のように説明されます。
「ポリモーフィズム」とは、「抽象クラス」や「インターフェース」などを利用してメソッドの呼び出し方法を共通化し、さらに「オーバーライド」させることで同じメソッドを呼び出しても、実際のインスタンス毎にその挙動を変化させようとするものです。
オブジェクト指向 - Java入門 - IT専科
分かりやすくいうと、インスタンスの特徴をざっくり捉えることで、処理をまとめて記述できる機能 と理解できます。
よく見る例ですが、親に抽象クラスの 動物クラス , 子に 猫 ,と 犬 の2クラスを用意して簡単に説明したいと思います。必要最低限のソースだけ書きます。
public abstract class Animal {
abstract void bark(); //鳴き声の抽象メソッド
}
public class Cat extends Animal{
@Override
void bark() {
System.out.println("ニャーニャー");
}
}
public class Dog extends Animal{
@Override
void bark() {
System.out.println("ワンワン");
}
}
犬と猫クラスの bark メソッドを実行してみたいと思います。まず動物のざっくりとした変数の型を用意して、猫や犬の具体的なインスタンスを代入します。入れ物の型は抽象的、中身 (インスタンス) の型は具体的な感覚です。←これ大事です。左辺は親で、右辺は子みたいに暗記する勉強法はお勧めしません (初学者の混乱のもと)。
public class Test {
public static void main(String[] args) {
Animal tama = new Cat();
Animal poti = new Dog();
tama.bark(); //「ニャーニャー」と出力
poti.bark(); //「ワンワン」と出力
}
}
上の例だと2回 動物型を宣言していますが、下のように動物型の配列を用意して、猫と犬のインスタンスをまとめて格納することも出来ます。もし動物型を継承する子クラスが増えたとしても、配列とforループを使えばコードを増やさず記述することが出来ますね。多態性の大きなメリットです。
public class Test {
public static void main(String[] args) {
//配列型なので拡張性あり、コードも短くスッキリ
Animal[] pets = {new Cat(),new Dog()};
for (int i=0;i< pets.length;i++) {
pets[i].bark();
}
}
}
実務で使用するコードは、大規模なものです。例えば、一つの基底クラスに対して十数以上のクラスが継承しているケースもあります。また複雑な条件分岐に従って、適切なクラスをインスタンス化してBL実行することが求められます。そうした問題は多態性の機能を用いて再利用性や拡張性を高める必要があります。私見に基づいて、以下に2パターン示します。
Command パターンについて詳しくは下記書籍などで述べられてます。
『Java言語で学ぶ デザインパターン入門』(結城浩 ソフトバンクパブリッシング株式会社出版 2001年)
簡単に説明すると、要求を Command オブジェクトにして、適切な引数を渡して要求先のオブジェクトのメソッドを実行するパターンです。Javaの設計思想の一つです。分かりやすくするために超シンプルな例を下に示します。
コマンドの基底クラスを作成します。
//コマンドのスーパークラス
public abstract class Command {
//要求内容を実行する抽象メソッド
public abstract void execute();
}
次に基底クラスを継承して、先ほどの例のように犬と猫のコマンドオブジェクトを作成します。
//猫コマンドクラス
public class CatCommand extends Command{
@Override
public void execute() {
System.out.println("ニャーニャー");
}
}
//犬コマンドクラス
public class DogCommand extends Command{
@Override
public void execute() {
System.out.println("ワンワン");
}
}
最後に CommandFactory クラスを作成します。このクラスの役目は、引数から条件分岐に従って適切なコマンドオブジェクトのインスタンスを返却することです。つまり、猫と引数を渡したら、猫クラスのインスタンスを返却。犬と引数に渡したら、犬のインスタンスを返却してもらいます。コードを実行するユーザの窓口になります。返り値の型を Command 型に設定できるのは、多態性の恩恵ですね。
public class CommandFactory {
//コマンド型を返却するメソッド
public static Command commandIF (String name) {
if(name.equals("cat")) {
//猫コマンドオブジェクトを返却
return new CatCommand();
}
if(name.equals("dog")) {
//犬コマンドオブジェクトを返却
return new DogCommand();
}
return null;
}
}
それでは必要なパーツが準備できたので、実際に main() メソッドで実行していきましょう。
public class User {
public static void main(String[] args) {
Command cmd = CommandFactory.commandIF("cat");
cmd.execute();//ニャーニャー と出力
}
}
たった2行で実行できましたね。犬のメソッドを実行したかったら、commandIF の引数を dog
に変更すればよいだけです。こういった設計思想は本当に大事です。コードのメンテナンスコストを下げたり、クラスの再利用性を高めるのに多態性は欠かせない要素ということです。
大規模システムでは、設定ファイルやDBのマスタテーブルなどでシステム設定を管理することが普通だと思います。なぜそうするかというと、システム運用者が設定値を簡単に変更できることで運用負荷を下げたり改修コストを下げるためです。コードの中に設定値が埋め込まれていると、その都度コードを改修をしてからコンパイル、デプロイが必要になります。システム設定に実行したいblクラスを設定 (業務ごとに呼び出したいクラスを設定) している場合、java.lang.ClassLoader
というクラスと多態性が大活躍します。下に簡単な例を示します。
public class ReflectionTest {
public static void main(String[] args) {
//className を外部から取得 (propertiesファイル読み込みや、テーブルマスタから取得)
//今回は猫オブジェクトを直接設定↓
String className ="sample.CatCommand";
Command cmd = null;
try {
//インスタンスを生成、Command型にキャスト変換
cmd = (Command) Class.forName(className).newInstance();
cmd.execute(); //ニャーニャー と出力
} catch (Exception e) {
e.printStackTrace();
}
}
}
Java で開発していると、ほとんどの人がSpring などのフレームワークの恩恵を享受していると思います。
フレームワークの裏側ではリフレクションや多態性が利用されており、アノテーションなど便利な機能が提供されています。
Dependency Injection (依存性注入) て何?て興味がある人は下の記事も合わせてお読みください。
【Java】リフレクションによるDIコンテナ実装サンプル
いかがだったでしょうか。オブジェクト指向を実際に便利だと感じるには、個人で書くような短いコードでは実感しづらい面があります。多態性はその最たる例だと思います。本記事を通して、少しでも多態性の実用性を理解してもらえたなら幸いです。自分もまだまだ未熟なので、オブジェクト指向をもっと学びたいと思います。もし間違いなどありましたら訂正しますのでご連絡下さい。