こんにちは、かつコーチです。
ここまでクラス・オブジェクト・カプセル化を解説してきましたが、今回は複数のクラスに共通する部分をまとめられる「継承」と、セットで使うsuperキーワードを解説します。
継承とは?
共通部分を親クラスにまとめる仕組み
継承とは、あるクラスが別のクラスのフィールドやメソッドを引き継ぐ仕組みのことです。
引き継ぐ元のクラスを「親クラス(スーパークラス)」、引き継ぐ側のクラスを「子クラス(サブクラス)」と呼びます。
例えば「犬」と「猫」はどちらも「動物」の一種で、名前や年齢、鳴くという動作を共通して持っています。
// 親クラス
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + "がごはんを食べています");
}
}
// 子クラス
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age); // 親クラスのコンストラクタを呼び出す
}
public void bark() {
System.out.println(name + "が吠えました!");
}
}
extendsキーワードを使うことで、DogクラスはAnimalクラスを継承します。
これによりDogクラスはeat()メソッドを自分で書かなくても使えるようになります。
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("ポチ", 3);
dog.eat(); // Animalクラスのメソッド:ポチがごはんを食べています
dog.bark(); // Dogクラス独自のメソッド:ポチが吠えました!
}
}
なぜ継承が必要なのか
継承を使わない場合、DogクラスとCatクラスにそれぞれname・age・eat()を重複して書く必要があります。
// 継承なしだと同じコードが重複してしまう
public class Dog {
String name;
int age;
void eat() { System.out.println(name + "がごはんを食べています"); }
void bark() { System.out.println(name + "が吠えました!"); }
}
public class Cat {
String name;
int age;
void eat() { System.out.println(name + "がごはんを食べています"); } // 重複
void meow() { System.out.println(name + "がニャーと鳴きました!"); }
}
共通部分をAnimalクラスにまとめて継承すれば、修正が必要になったときも1箇所を直すだけで済みます。
これが継承の大きなメリットである「コードの再利用性」です。
superキーワードの役割
親クラスのコンストラクタを呼び出す
super(...)は、親クラスのコンストラクタを呼び出すための構文です。
子クラスのコンストラクタの先頭で使う必要があります。
public class Dog extends Animal {
private String breed; // 犬種(子クラス独自のフィールド)
public Dog(String name, int age, String breed) {
super(name, age); // Animalのコンストラクタでname, ageを初期化
this.breed = breed;
}
}
もしsuper(...)を書かなかった場合、Javaは自動的に親クラスの「引数なしコンストラクタ」を呼び出そうとします。
親クラスに引数なしコンストラクタが存在しない場合は、コンパイルエラーになります。
親クラスのメソッドを呼び出す
super.メソッド名()という形で、親クラスのメソッドを明示的に呼び出すこともできます。
これは、子クラスで同名のメソッドを上書き(オーバーライド)しつつ、親の処理も活かしたい場合によく使われます。
public class Dog extends Animal {
@Override
public void eat() {
super.eat(); // 親クラスのeat()も実行する
System.out.println("ドッグフードをおかわりしました");
}
}
ポチがごはんを食べています
ドッグフードをおかわりしました
よくあるつまずきポイント・エラー対処
protectedとprivateの違いでハマる
継承を学び始めた頃、多くの人が親クラスのフィールドをprivateのままにしてしまい、子クラスから使えずに困ります。
❌Before
public class Animal {
private String name; // 子クラスから直接アクセスできない
public Animal(String name) {
this.name = name;
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void bark() {
System.out.println(name + "が吠えました!"); // コンパイルエラー
}
}
エラーメッセージは以下のようになります。
error: name has private access in Animal
✅After
子クラスからも直接アクセスさせたいフィールドには、privateではなくprotected(同一パッケージ・子クラスからアクセス可能な修飾子)を使います。
public class Animal {
protected String name; // 子クラスからもアクセス可能
public Animal(String name) {
this.name = name;
}
}
私が初めて継承を使ったとき、最初はすべてのフィールドをprivateにしていたため、子クラスから軒並みアクセスできずコンパイルエラーの山になった経験があります。
「子クラスに使わせたいフィールドかどうか」を意識してprotectedかprivateかを選ぶ、という判断基準を持つとハマりにくくなります。
多重継承ができないと勘違いする
Javaのクラスは、複数のクラスを同時に継承すること(多重継承)ができません。
❌Before
// コンパイルエラー:クラスは1つしかextendsできない
public class Dog extends Animal, Pet {
}
error: '{' expected
✅After
複数の特性を持たせたい場合は、後の記事で解説するinterface(インターフェース)を使います。
1つの親クラスを継承しつつ、複数のインターフェースを実装するのがJavaの基本パターンです。
public class Dog extends Animal implements Trainable {
// ...
}
応用・一歩先の使い方
instanceofで型を確認する
継承関係にあるオブジェクトは、instanceof演算子でその型を確認できます。
これは、複数の子クラスをまとめて扱う際に、特定の型だけ特別な処理をしたい場合に役立ちます。
Animal animal = new Dog("ポチ", 3);
if (animal instanceof Dog dog) { // Java 16以降のパターンマッチング構文
dog.bark();
}
if (animal instanceof Dog dog)という書き方は、型チェックと同時にキャスト済みの変数dogを得られる、Java 16以降で導入されたパターンマッチングです。
継承の理解が深まったら、次の記事で扱う「オーバーライドとポリモーフィズム」と組み合わせて使えるようになります。
まとめ
この記事のポイント
- 継承は
extendsを使い、親クラスのフィールド・メソッドを子クラスに引き継ぐ仕組み - super(…)は親クラスのコンストラクタ呼び出し、super.メソッド名()は親クラスのメソッド呼び出しに使う
- 子クラスからアクセスさせたいフィールドは
privateではなくprotectedにする - Javaのクラスは多重継承ができず、複数の特性を持たせたい場合はインターフェースを使う
次に読むべき記事
継承の基本を理解したら、次は子クラスで親クラスのメソッドを上書きする「メソッドのオーバーライドとポリモーフィズム」に進みましょう。
タグ: Java, 初心者向け, オブジェクト指向