Javaの継承とポリモーフィズムは、オブジェクト指向プログラミング(OOP)において非常に重要な概念です。継承を利用することで、コードの再利用性を高め、共通の機能を親クラスにまとめることができます。また、ポリモーフィズムを活用することで、異なるオブジェクトが同じメソッドを持ちながらも、その動作を異なる形で実装できます。このガイドでは、これらの概念を詳しく解説し、実際のサンプルコードとその出力結果を通して理解を深めます。
継承とは何か?
継承は、あるクラスが既存のクラスのプロパティやメソッドを引き継ぐことを指します。Javaでは、`extends`キーワードを使用して、サブクラス(子クラス)がスーパークラス(親クラス)を継承します。これにより、コードの冗長性を減らし、共通の機能を再利用することが可能になります。
class Animal { String name; void sleep() { System.out.println(name + " is sleeping."); } } class Dog extends Animal { void bark() { System.out.println(name + " is barking."); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.name = "Pochi"; dog.sleep(); // 出力: Pochi is sleeping. dog.bark(); // 出力: Pochi is barking. } }
この例では、`Animal`という親クラスから`Dog`という子クラスが継承されています。親クラスの`sleep`メソッドは子クラスでそのまま使用でき、さらに`Dog`クラス固有の`bark`メソッドも追加されています。これにより、コードの再利用が容易になり、継承の利点が示されています。
ポリモーフィズムとは何か?
ポリモーフィズムは、同じインターフェースや親クラスに基づいて異なる型のオブジェクトが異なる振る舞いを持つことを可能にする概念です。Javaでは、メソッドのオーバーライドを通じて実現され、サブクラスが親クラスのメソッドを自分自身の実装に置き換えることができます。これにより、動的なメソッドの呼び出しが可能になります。
class Animal { void sound() { System.out.println("Some sound..."); } } class Dog extends Animal { @Override void sound() { System.out.println("Bark"); } } class Cat extends Animal { @Override void sound() { System.out.println("Meow"); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); Animal myCat = new Cat(); myDog.sound(); // 出力: Bark myCat.sound(); // 出力: Meow } }
この例では、`Animal`クラスの`sound`メソッドが`Dog`および`Cat`クラスでオーバーライドされています。`myDog`と`myCat`という異なるオブジェクトが、同じ`sound`メソッドを持ちながらも、それぞれ異なる出力を生成しています。これがポリモーフィズムの基本的な機能であり、動的に異なる振る舞いを実現することができます。
スーパークラスのメソッドを呼び出す
Javaでは、サブクラスがスーパークラスのメソッドをオーバーライドすることができますが、サブクラス内からスーパークラスのメソッドを呼び出すことも可能です。これには`super`キーワードを使用します。サブクラスがスーパークラスの機能を拡張するために、この技法をよく使用します。
class Animal { void sound() { System.out.println("Some generic animal sound."); } } class Dog extends Animal { @Override void sound() { super.sound(); // スーパークラスのメソッドを呼び出す System.out.println("Bark"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.sound(); // 出力: Some generic animal sound. Bark } }
この例では、`Dog`クラスが`Animal`クラスの`sound`メソッドをオーバーライドしていますが、その中で`super.sound()`を使ってスーパークラスのメソッドも呼び出しています。このように、親クラスのメソッドを部分的に活用しつつ、サブクラス固有の動作を追加することができます。
抽象クラスとインターフェースを用いたポリモーフィズム
Javaでは、抽象クラスやインターフェースを用いることで、さらに柔軟なポリモーフィズムを実現できます。抽象クラスはインスタンス化できないクラスであり、具体的なメソッドの実装はサブクラスに任せます。一方、インターフェースは、クラスが実装すべきメソッドの定義を提供します。
abstract class Animal { abstract void sound(); } class Dog extends Animal { @Override void sound() { System.out.println("Bark"); } } class Cat extends Animal { @Override void sound() { System.out.println("Meow"); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); Animal myCat = new Cat(); myDog.sound(); // 出力: Bark myCat.sound(); // 出力: Meow } }
ここでは、`Animal`クラスを抽象クラスとして定義し、その中で`sound`メソッドを抽象メソッドとして宣言しています。`Dog`や`Cat`クラスは、このメソッドを具体的に実装しています。抽象クラスを使用することで、サブクラスが共通のメソッドを持つことを強制しつつ、サブクラスごとに異なる振る舞いを提供できます。
まとめ
継承とポリモーフィズムは、Javaのオブジェクト指向プログラミングにおいて非常に強力なツールです。継承を使うことで、コードの再利用と保守性を向上させることができ、ポリモーフィズムを活用することで、柔軟で拡張性の高いプログラムを作成することができます。特に大規模なシステムや複雑なアプリケーションにおいて、これらの概念を正しく理解し使用することは、非常に重要です。この記事を通して、Javaの継承とポリモーフィズムの基本的な使い方を学び、実際の開発で活用してみてください。