1. 継承とは何か?
継承は、既存のクラスを基にしてそのプロパティやメソッドを「受け継ぎ」、新しいクラスを作る仕組みです。新しいクラスはサブクラス(子クラスとも)、元のクラスは親クラス(または基底クラス)と呼ばれます。
たとえば古いレシピをベースに新しいケーキを作るようなものです。良いところはすべて受け継ぎ、材料を少し追加して——はい、「おばあちゃんのレシピ+秘密の材料のケーキ」の出来上がり、という感じです。
- 親クラス — 共通のプロパティと振る舞いを持ちます。
- サブクラス — それらを継承し、さらに独自のものを追加したり振る舞いを変更できます。
たとえば「猫は動物である」: すべての猫は動物ですが、すべての動物が猫ではありません。動物を基底クラス、猫をそのサブクラスにできます。同様に犬もサブクラスにできます。
また「円は図形である」: すべての円は図形ですが、すべての図形が円ではありません。図形を親クラス、円や三角形をその継承側にできます。Java ではこれらの関係をキーワード extends で表現します。
なぜ継承が必要なのか?
よくある疑問: なぜ単にあるクラスのコードを別のクラスへコピペするだけではいけないのか? なぜ継承が生まれたのか?
- コードの再利用: 共通部分は基底クラスにまとめます。フィールドやメソッドを重複させる必要はなく、サブクラスに自動的に現れます。たとえば大学向けのアプリでは、人を基底クラスにして少なくとも名前と誕生日を持たせ、「学生」「教員」をそれぞれサブクラスとして独自のプロパティを追加できます。
- 拡張と特化: 必要なところだけ新しいプロパティやメソッドを追加したり、既存の振る舞いを変更できます。すべての動物は鳴きますが、猫はニャー、犬はワンと鳴く、といった具合です。また、猫には木登りの能力を追加できます(基底クラスにはない能力)。
- 階層の構造化: コードがより論理的で分かりやすくなります。たとえばクラス Animal、Dog、Cat があれば、Dog と Cat が Animal の特殊化であることが一目で分かります。
例. もし Java に継承がなければ、eat()、sleep() のようなメソッドやフィールド name を動物ごとのクラスにすべてコピーしなければなりません。さらに明日 breathe() メソッドを追加したくなったら、何十ものクラスを修正する羽目になります! 継承があれば、呼吸の機能を基底クラスに追加するだけで済みます。
2. 継承の構文: extends
Java で継承を宣言するには? とても簡単です。キーワード extends を使います。
class RoditelskiyKlass {
// フィールドとメソッド
}
class Podklass extends RoditelskiyKlass {
// 追加のフィールドとメソッド
}
例 1: 動物
// 親クラス
class Animal {
String name;
void eat() {
System.out.println(name + " は食べています。");
}
}
// サブクラス
class Cat extends Animal {
void meow() {
System.out.println(name + " はこう言う: ニャー!");
}
}
ここで何が起きている?
- クラス Cat はクラス Animal のすべてのフィールドとメソッドを継承します。
- つまり、Cat は明示的に書かなくてもフィールド name とメソッド eat() を既に持っています。
- 加えて Cat には独自のメソッド meow() が追加されます。
使用例
public class Main {
public static void main(String[] args) {
Cat kitty = new Cat();
kitty.name = "Barsik";
kitty.eat(); // Barsik は食べています。 (継承済み)
kitty.meow(); // Barsik はこう言う: ニャー! (独自メソッド)
}
}
できました! 重複を避け、最小限のコードで新しいクラスを手に入れました。
3. サブクラスは具体的に何を継承するか?
Java でサブクラスが継承するのは次のとおりです:
- public と protected のフィールドとメソッド(基底クラスのもの)。
- package-private のフィールドとメソッド(同一パッケージ内であれば)。
継承されないもの:
- コンストラクタ(サブクラスで明示的に宣言する必要があります)。
- private のフィールドとメソッド(それらは基底クラスの内部でのみアクセス可能)。
- static 初期化ブロック(継承はされませんが、各クラスが独自に持てます)。
- package-private のメンバー(サブクラスが別パッケージにある場合)。
イラスト
class Animal {
public String name;
private int age;
public void eat() {
System.out.println("食べる");
}
private void secret() {
System.out.println("秘密!");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("ワン!");
}
}
- Dog はフィールド name とメソッド eat() を継承します。
- Dog からはフィールド age とメソッド secret() は見えません — どちらも private です。
4. Java における継承の制約
Java はわりと厳格で、いくつかルールがあります。
クラスの単一継承のみ: 各クラスはただ 1 つのクラスからしか継承できません。ただし、そのクラスに対して複数のサブクラスを作ることは可能です。
class Dog extends Animal { ... } // OK
class Dog extends Animal, Pet { ... } // エラー! これはできません
インターフェースの多重継承は可能ですが、これは別のトピックなので後で扱います.
クラス Object は Java のすべてのクラスの祖先です。たとえ明示的に extends を書かなくても、あなたのクラスは暗黙に Object を継承します。
5. 実用的な例
「動物」アプリを作り続けてみましょう。
ステップ 1. 基底クラス Animal
class Animal {
String name;
void eat() {
System.out.println(name + " は食べています。");
}
}
ステップ 2. サブクラス Cat
class Cat extends Animal {
void meow() {
System.out.println(name + " はこう言う: ニャー!");
}
}
ステップ 3. サブクラス Dog
class Dog extends Animal {
void bark() {
System.out.println(name + " はこう言う: ワン!");
}
}
ステップ 4. クラスを使ってみる
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "Murka";
cat.eat(); // Murka は食べています。
cat.meow(); // Murka はこう言う: ニャー!
Dog dog = new Dog();
dog.name = "Sharik";
dog.eat(); // Sharik は食べています。
dog.bark(); // Sharik はこう言う: ワン!
}
}
何が得られたか?
- Cat と Dog の両方が、基底クラスからフィールド name とメソッド eat() を共用しています。
- 各クラスは独自の振る舞いを追加します。猫には meow()、犬には bark()。
- 動物の食べ方を変更したい場合は、Animal クラスの eat() を変更するだけで、すべての継承先に自動的に反映されます。
6. 役立つ要点
可視化: クラス階層
階層は次のように表せます:
. Animal
/ \
Cat Dog
- Animal — 基底クラスで、動物に共通するものを含みます。
- Cat と Dog — サブクラスで、固有の特徴を追加します。
コンストラクタと継承について少し
コンストラクタは継承されません。サブクラスでコンストラクタを定義しない場合、Java は自動的に引数なしコンストラクタを追加します(親にそれがある場合)。親クラスに引数つきコンストラクタしかない場合は、サブクラスで super(...) を使って明示的に呼び出す必要があります。短い例:
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // 親クラスのコンストラクタを呼び出す
}
}
7. 継承を使う際の典型的なミス
誤りその1: 複数のクラスから同時に継承しようとする。
Java では class Cat extends Animal, Pet のようには書けません。親は 1 つだけです!
誤りその2: private のフィールドやメソッドがサブクラスから見えると期待する。
private メンバーは継承されません(正確には存在しますが、サブクラスからアクセスできません)。継承先にアクセスさせたい場合は protected を使いましょう。
誤りその3: 親のコンストラクタ呼び出しを忘れる。
基底クラスに引数なしコンストラクタがなく、かつ super(...) を明示的に呼び出さないと、コンパイルエラーになります。
誤りその4: 「コードを節約したいから」という理由だけで継承を使う。
クラス間に「is-a(〜である)」の関係がないなら、継承は使うべきではありません。たとえば「人間」は「自動車」から継承すべきではありません。たとえ両者に speed というフィールドがあっても、です。
GO TO FULL VERSION