【Java入門シリーズ】第10章 継承とポリモーフィズム 〜オブジェクト指向プログラミングの核心〜

【Java入門シリーズ】第10章: 継承とポリモーフィズム 〜オブジェクト指向プログラミングの核心〜

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()メソッドでまず親クラスのAnimalmakeSound()メソッドを呼び出し、その後に独自の処理を追加しています。

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型の変数myDogmyCatは、実際にはDogCatのインスタンスを指しており、makeSound()メソッドを呼び出すと、それぞれのクラスの実装が実行されます。

10.5.2 ポリモーフィズムのメリット

  • 柔軟なコード設計: 親クラスの型で処理を行うことで、異なるサブクラスのオブジェクトを同じ処理で扱うことができます。これにより、コードの柔軟性が向上します。
  • コードの簡潔化: 同じ処理を複数のサブクラスで行う際に、ポリモーフィズムを活用すると、処理を統一でき、冗長なコードを避けることができます。

10.6 Javaの継承とポリモーフィズムまとめ

この章では、Javaの継承ポリモーフィズムについて詳しく学びました。継承を利用することで、既存のクラスの機能を再利用し、新しいクラスを効率的に作成できることが理解できました。また、メソッドのオーバーライドやsuperキーワードの使い方を通して、親クラスと子クラスの関係を柔軟に扱う方法を習得しました。

さらに、抽象クラスとインターフェースの違いを理解し、ポリモーフィズムを利用して、異なるクラスのオブジェクトを同じように扱う仕組みも学びました。これにより、Javaのオブジェクト指向の真髄に触れることができ、より柔軟で拡張性の高いプログラムを設計できるようになります。

次章では、Javaにおける入出力処理を学び、ファイル操作やユーザー入力の処理方法を探っていきます。