この記事の要点
• 継承は既存クラスを拡張して新しいクラスを作る仕組み
• extends でスーパークラスを指定、super() でコンストラクタ呼び出し
• @Override でメソッドを上書きし、独自の振る舞いを追加
このシリーズについて
「Java入門」シリーズの第21回です。本記事では**継承(Inheritance)**の基本を学びます。
前提条件: #12 - クラスの基本、#13 - コンストラクタとメソッド を理解していること。
継承とは
継承は、既存のクラス(スーパークラス / 親クラス)の機能を引き継いで、新しいクラス(サブクラス / 子クラス)を定義する仕組みです。
// Java 21 - スーパークラス
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
// Java 21 - サブクラス
public class Dog extends Animal {
public Dog(String name) {
super(name); // スーパークラスのコンストラクタを呼び出す
}
public void bark() {
System.out.println(name + " is barking: Woof!");
}
}
Dog は Animal を継承しているため、eat() メソッドと name フィールドを持ちます。さらに独自の bark() を追加しています。
ポイント: 継承を使うと、共通の機能を親クラスにまとめ、個別の機能だけを子クラスに追加できます。コードの重複を減らし、保守性が向上します。
extends キーワード
サブクラスを定義するには extends を使います。
public class サブクラス extends スーパークラス {
// フィールド、コンストラクタ、メソッド
}
| 用語 | 意味 |
|---|---|
| スーパークラス(親クラス) | 継承元のクラス |
| サブクラス(子クラス) | 継承先のクラス。スーパークラスの機能 + 独自の機能を持つ |
Java は単一継承のみサポートします。複数のクラスを同時に継承(extends A, B)することはできません。
注意: Java では1つのクラスしか継承できませんが、後に学ぶ インターフェース(interface)は複数実装できます。
super() でスーパークラスのコンストラクタを呼び出す
サブクラスのコンストラクタは、最初にスーパークラスのコンストラクタを呼び出す必要があります。
// Java 21 - super() の明示的な呼び出し
public class Cat extends Animal {
private int lives;
public Cat(String name, int lives) {
super(name); // Animal のコンストラクタを呼び出す
this.lives = lives;
}
public void meow() {
System.out.println(name + " is meowing: Meow!");
}
}
super(name) はコンストラクタの最初の行に書く必要があります。書かない場合、コンパイラが自動で super() (引数なし)を挿入しますが、スーパークラスに引数なしコンストラクタがないとコンパイルエラーになります。
ヒント: super() はスーパークラスのコンストラクタ、this() は同じクラスの別のコンストラクタを呼び出します。どちらも最初の行に書く必要があるため、両方を同時に使うことはできません。
フィールド・メソッドの継承
サブクラスはスーパークラスの public / protected フィールドとメソッドを自動的に持ちます。
// Java 21 - 継承されたメソッドを使う例
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Pochi");
dog.eat(); // Animal から継承したメソッド
dog.bark(); // Dog 独自のメソッド
}
}
実行結果:
Pochi is eating.
Pochi is barking: Woof!
Dog クラスには eat() の定義はありませんが、Animal を継承しているため呼び出せます。
メソッドオーバーライド
サブクラスでスーパークラスのメソッドを**上書き(Override)**することができます。
// Java 21 - メソッドオーバーライドの例
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name + " is pecking seeds.");
}
public void fly() {
System.out.println(name + " is flying.");
}
}
Bird の eat() は Animal の eat() を上書きしています。@Override アノテーションを付けることで、オーバーライドの意図を明示し、コンパイラがチェックしてくれます。
ポイント: @Override は省略可能ですが、必ず付けましょう。メソッド名のタイポ(eat のつもりで eatt と書いてしまう等)をコンパイル時に検出できます。
オーバーライドのルール
| ルール | 説明 |
|---|---|
| メソッド名・引数が同じ | 異なるとオーバーロード(別メソッド扱い) |
| 戻り値の型は同じか、共変 | 戻り値を子クラスの型に変更するのは許可(共変戻り値型) |
| アクセス修飾子は同等以上 | protected → public は OK。public → private は NG |
| static / final メソッドは不可 | static は継承しない。final は上書き禁止 |
アクセス修飾子と継承の関係
スーパークラスのフィールド・メソッドがサブクラスから見えるかどうかは、アクセス修飾子で決まります。
| 修飾子 | 同じクラス | 同じパッケージ | サブクラス(別パッケージ) | 外部 |
|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
| (なし / package-private) | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
継承で重要なポイント:
privateフィールド・メソッドはサブクラスから直接アクセスできない(継承はされているが見えない)protectedはサブクラスからアクセスできるが、外部には非公開publicはどこからでもアクセス可能
// Java 21 - アクセス修飾子の違い
public class Animal {
private String secret = "秘密"; // サブクラスから見えない
protected String name; // サブクラスから見える
public void eat() { /* ... */ } // どこからでも呼べる
}
public class Dog extends Animal {
public void printInfo() {
// System.out.println(secret); // コンパイルエラー(private)
System.out.println(name); // OK(protected)
}
}
注意: protected は同じパッケージ内からもアクセスできます。完全に継承先だけに公開したい場合でも、パッケージを分けない限り他のクラスから見えてしまいます。
実行例:Animal → Dog の継承
// Java 21 - 継承の動作確認
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Shiba");
dog.eat(); // Animal から継承
dog.bark(); // Dog 独自
Bird bird = new Bird("Sparrow");
bird.eat(); // Bird でオーバーライド
bird.fly(); // Bird 独自
}
}
実行結果:
Shiba is eating.
Shiba is barking: Woof!
Sparrow is pecking seeds.
Sparrow is flying.
Bird の eat() はオーバーライドされているため、Animal の実装ではなく Bird の実装が呼ばれます。
super でスーパークラスのメソッドを呼び出す
オーバーライドしたメソッドの中で、スーパークラスの元の実装を呼び出すこともできます。
// Java 21 - super でスーパークラスのメソッドを再利用
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
@Override
public void eat() {
super.eat(); // Animal の eat() を呼び出す
System.out.println(" (pecking seeds)");
}
}
実行結果:
Sparrow is eating.
(pecking seeds)
super.eat() で親の処理を実行してから、追加のメッセージを出力しています。
ヒント: super はフィールドアクセスにも使えます。super.name でスーパークラスの name を明示的に参照できます(ただし同名フィールドを子で再定義するのは混乱の元なので避けましょう)。
プログラミングのベストプラクティス
| やるべきこと | 理由 |
|---|---|
| @Override を必ず付ける | タイポや引数ミスをコンパイル時に検出 |
| protected を適切に使う | サブクラスにのみ公開したいフィールド・メソッド |
| 継承は「is-a 関係」のときだけ | Dog is an Animal → 自然。Car is an Engine → 不自然(コンポジションを使う) |
| 深い継承階層を避ける | 3階層以上になると理解困難。設計を見直す |
注意: 継承は強力ですが、使いすぎると密結合になります。「コードの再利用」だけが目的なら、後に学ぶ コンポジション(has-a 関係)や インターフェース の方が適切なケースも多いです。
次のステップ
次回以降では以下のトピックを扱います。
- ポリモーフィズム:
Animal animal = new Dog();のようなアップキャスト - abstract クラス: 抽象メソッドを持つクラス
- インターフェース: 複数実装可能な型の契約
Java入門 #22 - ポリモーフィズムとアップキャスト(今後追加予定)
参考リソース
- Oracle Java SE 21 - Object — すべてのクラスの親クラス
- Inheritance (The Java™ Tutorials) — 公式チュートリアル
- Effective Java (Joshua Bloch) — Item 18: Favor composition over inheritance