Javaの最大の特徴の一つが、オブジェクト指向プログラミング(OOP)です。オブジェクト指向の中心的な概念である継承(Inheritance)とポリモーフィズム(Polymorphism)は、コードの再利用性を高め、システム全体の設計をより柔軟で拡張性の高いものにします。
この章では、オブジェクト指向の要である継承とポリモーフィズムについて詳しく解説します。具体的には、次のトピックをカバーします:
- 継承の基本
- メソッドのオーバーライド
super
キーワードの使い方- 抽象クラスとインターフェース
- ポリモーフィズムの基本とその応用
10.1 継承とは?
継承(Inheritance)とは、既存のクラスをベースにして新しいクラスを作成する仕組みです。新しいクラス(サブクラスまたは子クラス)は、既存のクラス(スーパークラスまたは親クラス)の属性(フィールド)や振る舞い(メソッド)を引き継ぎます。これにより、コードの重複を避け、より効率的にクラスを設計することができます。
10.1.1 継承の構文
継承を行うには、extends
キーワードを使います。基本構文は以下の通りです。
class サブクラス extends スーパークラス {
// サブクラスの独自のフィールドとメソッド
}
例えば、Animal
という親クラスからDog
という子クラスを継承する例を見てみましょう。
// 親クラス
class Animal {
String name;
public void makeSound() {
System.out.println("動物の鳴き声");
}
}
// 子クラス
class Dog extends Animal {
public void makeSound() {
System.out.println("ワンワン");
}
}
10.1.2 継承のメリット
継承のメリットは、次のような点にあります:
- コードの再利用: 一度定義したクラスを他のクラスで使い回すことができ、コードの重複を避けることができます。
- 拡張性: 親クラスの基本機能を持ちながら、新たな機能を追加することで、サブクラスに特化した動作を実装できます。
- 保守性の向上: 親クラスの変更が子クラスにも反映されるため、保守が容易になります。
10.2 メソッドのオーバーライド
継承を使うと、サブクラスはスーパークラスのメソッドをそのまま利用できますが、メソッドのオーバーライド(Override)を行うことで、親クラスで定義されたメソッドの振る舞いをサブクラスで再定義することも可能です。これにより、サブクラスは親クラスの基本機能を持ちながら、独自の振る舞いを実装できます。
10.2.1 オーバーライドの基本構文
オーバーライドを行うには、サブクラスで親クラスと同じ名前、同じ引数のメソッドを定義します。
@Override
public void メソッド名() {
// オーバーライドされたメソッドの処理
}
例えば、Animal
クラスのmakeSound()
メソッドをDog
クラスでオーバーライドする例を示します。
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("ワンワン");
}
}
10.2.2 オーバーライドのルール
オーバーライドには、いくつかの重要なルールがあります。
- メソッド名、引数、戻り値が一致すること: オーバーライドするメソッドは、親クラスと完全に一致する必要があります。
- アクセス修飾子は緩めることができる: オーバーライドするメソッドは、親クラスのメソッドと同じか、より緩やかなアクセス修飾子を定義できます。例えば、親クラスで
protected
なメソッドは、サブクラスでpublic
にすることができますが、逆にprivate
にはできません。 @Override
アノテーションの使用: オーバーライドされたメソッドに@Override
アノテーションを付けることで、コンパイラがオーバーライドが正しく行われているかをチェックします。
次の例では、Dog
クラスでmakeSound()
メソッドをオーバーライドし、親クラスのAnimal
クラスのmakeSound()
メソッドとは異なる振る舞いを実装しています。
class Animal {
public void makeSound() {
System.out.println("動物の鳴き声");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("ワンワン");
}
}
10.3 superキーワード 〜親クラスへのアクセス〜
サブクラスから親クラスのメソッドやコンストラクタを呼び出す際に使用するのがsuper
キーワードです。super
は、サブクラス内で親クラスの要素にアクセスするための重要なキーワードです。
10.3.1 親クラスのメソッド呼び出し
サブクラスでオーバーライドされたメソッド内から親クラスの同名メソッドを呼び出すには、super.メソッド名()
を使用します。これにより、サブクラスが独自の処理を追加したり、親クラスのメソッドを基にした処理を行うことができます。
class Dog extends Animal {
@Override
public void makeSound() {
super.makeSound(); // 親クラスのメソッドを呼び出し
System.out.println("ワンワン");
}
}
この例では、Dog
クラスのmakeSound()
メソッドでまず親クラスのAnimal
のmakeSound()
メソッドを呼び出し、その後に独自の処理を追加しています。
10.3.2 親クラスのコンストラクタ呼び出し
サブクラスのコンストラクタ内で、親クラスのコンストラクタを呼び出す場合にもsuper
を使用します。super()
を使うと、親クラスの初期化をサブクラスのコンストラクタで行えます。
class Animal {
String name;
// 親クラスのコンストラクタ
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
String breed;
// 子クラスのコンストラクタ
public Dog(String name, String breed) {
super(name); // 親クラスのコンストラクタを呼び出し
this.breed = breed;
}
}
この例では、Dog
クラスのコンストラクタが呼び出されると、まずsuper(name)
でAnimal
クラスのコンストラクタが実行され、その後でDog
クラスの初期化が行われます。
10.4 抽象クラスとインターフェース
Javaでは、クラスの設計をさらに柔軟にするために、抽象クラスとインターフェースが用意されています。これらは、共通のインターフェースを定義しつつ、具体的な実装を各サブクラスに委ねる仕組みです。
10.4.1 抽象クラスとは?
抽象クラス(abstract class)は、インスタンス化することができないクラスです。抽象クラスは、共通のプロパティやメソッドを定義し、詳細な実装はサブクラスに任せることを目的としています。
- 抽象メソッド: 実装が定義されていないメソッドで、サブクラスでオーバーライドして実装を提供する必要があります。
abstract class Animal {
String name;
public Animal(String name) {
this.name = name;
}
// 抽象メソッド
public abstract void makeSound();
// 具体的なメソッド
public void sleep() {
System.out.println(name + " は眠っています。");
}
}
上記のAnimal
クラスには、makeSound()
という抽象メソッドがあり、具体的な実装はサブクラスで行います。
10.4.2 抽象クラスのサブクラス
抽象クラスを継承したサブクラスでは、抽象メソッドを必ず実装する必要があります。
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " はワンワンと鳴きます。");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " はニャーニャーと鳴きます。");
}
}
これで、Dog
クラスやCat
クラスでmakeSound()
をそれぞれの動物に応じて実装できます。
10.4.3 インターフェースとは?
インターフェースは、抽象メソッドの集まりです。クラスがインターフェースを実装(implements)することで、インターフェースに定義されたメソッドをそのクラスで実装することが求められます。インターフェースを使うことで、異なるクラス間で共通の動作を強制的に実装させることができます。
interface Animal {
void makeSound(); // 抽象メソッド
}
インターフェースを実装するクラスは、implements
キーワードを使います。
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("ワンワン");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("ニャーニャー");
}
}
インターフェースは、クラスが複数の異なるインターフェースを実装することも可能であり、Javaにおける多重継承の代替として機能します。
10.4.4 抽象クラスとインターフェースの違い
特徴 | 抽象クラス | インターフェース |
---|---|---|
メソッドの実装 | 抽象メソッドと具体的なメソッドの両方を持てる | すべてのメソッドがデフォルトで抽象メソッド |
フィールドの定義 | フィールドを持つことができる | フィールドは定数のみ(static かつfinal ) |
継承 | 1つの親クラスしか継承できない | 複数のインターフェースを実装できる |
インスタンス生成 | インスタンス化できない | インスタンス化できない |
10.5 ポリモーフィズム 〜柔軟なメソッドの呼び出し〜
ポリモーフィズム(Polymorphism)は、オブジェクト指向プログラミングの重要な概念であり、同じメソッド呼び出しがオブジェクトの型によって異なる振る舞いをすることを指します。つまり、親クラスの型を使って、異なるサブクラスのオブジェクトを扱うことができ、実際のオブジェクトに応じて異なるメソッドが実行されます。
10.5.1 ポリモーフィズムの例
親クラスの型でサブクラスのオブジェクトを参照することで、ポリモーフィズムが実現できます。
class Animal {
public void makeSound() {
System.out.println("動物の鳴き声");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("ワンワン");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("ニャーニャー");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog(); // DogをAnimal型として扱う
Animal myCat = new Cat(); // CatをAnimal型として扱う
myDog.makeSound(); // 実際にはDogのmakeSound()が呼び出される
myCat.makeSound(); // 実際にはCatのmakeSound()が呼び出される
}
}
実行結果:
ワンワン
ニャーニャー
このように、Animal
型の変数myDog
やmyCat
は、実際にはDog
やCat
のインスタンスを指しており、makeSound()
メソッドを呼び出すと、それぞれのクラスの実装が実行されます。
10.5.2 ポリモーフィズムのメリット
- 柔軟なコード設計: 親クラスの型で処理を行うことで、異なるサブクラスのオブジェクトを同じ処理で扱うことができます。これにより、コードの柔軟性が向上します。
- コードの簡潔化: 同じ処理を複数のサブクラスで行う際に、ポリモーフィズムを活用すると、処理を統一でき、冗長なコードを避けることができます。
10.6 Javaの継承とポリモーフィズムまとめ
この章では、Javaの継承とポリモーフィズムについて詳しく学びました。継承を利用することで、既存のクラスの機能を再利用し、新しいクラスを効率的に作成できることが理解できました。また、メソッドのオーバーライドやsuper
キーワードの使い方を通して、親クラスと子クラスの関係を柔軟に扱う方法を習得しました。
さらに、抽象クラスとインターフェースの違いを理解し、ポリモーフィズムを利用して、異なるクラスのオブジェクトを同じように扱う仕組みも学びました。これにより、Javaのオブジェクト指向の真髄に触れることができ、より柔軟で拡張性の高いプログラムを設計できるようになります。
次章では、Javaにおける入出力処理を学び、ファイル操作やユーザー入力の処理方法を探っていきます。