「継承」とは既存のクラスの機能を再利用し、新しいクラスを作成するための重要なオブジェクト指向プログラミングの概念です。

ザックリいえば、クラスを進化させて新たなクラスを作りますよ!というのが継承です。
「継承」を理解し適切なコーディングを行うことで、コード再利用性の向上、メンテナンスの容易化などが可能になります。このページでは具体的なサンプルコードを用いながら継承の概念を理解し、継承を利用する際のコツや注意点をご説明します。
クラスの継承とは?
継承とは、既存のクラスの機能や特性を引き継いで新しいクラスを作ることを指します。
既存のクラスを「親クラス or スーパークラス」と呼び、そこから派生して作られた新しいクラスを「子クラス or サブクラス」と呼びます。継承を利用すると、親クラスの持つメソッドやフィールドをそのまま使用できるため、コードを一から書く必要がなくなり、非常に便利です。

例えば「動物」という親クラスがあり、その中に「食べる」という動作を定義しているとします。ここで「動物」クラスを継承して「犬」という子クラスを作成すると、「犬」クラスでも「食べる」という動作をそのまま利用することができます。また、「犬」クラスに特有の「吠える」という動作を追加することもできます。
継承を使うことで、共通の機能を親クラスにまとめ個々のクラスにはそのクラス固有の機能だけを追加する、ということができるようになります。こうすることで、コードの再利用性が高まり、変更が必要な場合も親クラスだけを修正すれば良いためメンテナンスが非常に楽になります。

つまるところ、継承はオブジェクト指向プログラミングの超・重要な概念なので、継承をうまく利用できないと、効率的なプログラム作成はできないということになります。
実際にイメージを深めるためには実際のコードを確認していくのが良いので、ここからは早速継承を利用したクラスの定義方法を解説していきます。
継承の構文ルール:extends
Javaで継承を使う際の基本的な構文は非常にシンプル。新しいクラスを定義するときに、既存のクラスを継承するためにextends
キーワードを使用するだけ。
class 子クラス extends 親クラス { // 子クラスのフィールドとメソッド }
具体的なサンプルコードを見てみましょう。まず「動物」という親クラスを作成します。このクラスには「食べる」というメソッドが含まれています。
class Animal { void eat() { System.out.println("This animal eats."); } }
次にこの「動物」クラスを継承して「犬」という子クラスを作成します。このタイミングでextends
キーワードを使用して、継承元の親クラスを指定します。
class Dog extends Animal { void bark() { System.out.println("The dog barks."); } }
この例では「犬」クラスは「動物」クラスを継承しています。そのため「犬」クラスは「動物」クラスの「食べる」メソッドをそのまま使用することができます。同時に「犬」クラスには「吠える」という新しいメソッドを利用できるようになります。

実際にこの「犬」クラスを使ってみると、以下のようになります。
public class Main { public static void main(String[] args) { Dog myDog = new Dog(); myDog.eat(); // 継承した「食べる」メソッドを呼び出す myDog.bark(); // 新しく追加した「吠える」メソッドを呼び出す } } // 出力結果 // This animal eats. // The dog barks.
このように、継承を使うことで親クラスの機能をそのまま再利用しつつ新しい機能を追加することができます。これによりコードの重複を避け、保守性の高いプログラムを作成することができます。
継承の構文は簡潔でわかりやすいため、オブジェクト指向プログラミングを学ぶ上で非常に重要な要素です。継承を正しく理解し、適切に活用することで、効率的で拡張性の高いプログラムを書くことができます。

ここから、継承に関する実践的な応用知識をご説明していきます。
メソッドのオーバーライド
メソッドのオーバーライドとは、親クラスに定義されたメソッドを子クラスで再定義することを指します。→つまり、子クラスは親クラスの基本的な動作を変更したり、拡張したりすることができるということ。
オーバーライドを行うためには、親クラスと同じメソッド名、同じ引数リスト、そして同じ戻り値の型(=同じシグニチャのセット)を持つメソッドを子クラスで定義します。また、オーバーライドするメソッドには@Override
アノテーションを付けることが推奨されます。これは、メソッドのオーバーライドが正しく行われているかをコンパイル時にチェックするためです。
- Qアノテーションとは?
- A
Javaプログラムに追加する特別な「注釈」のこと。コードにメタデータ(データに関するデータ)を提供するために使われます。アノテーションを使うと、コードの一部に特定の情報を付加し、その情報を基にコンパイラや実行環境が特定の動作をするように指示できます。
具体的な例を見てみましょう。まず、「動物」という親クラスに「食べる」メソッドを定義します。
class Animal { void eat() { System.out.println("This animal eats."); } }
次に、「犬」という子クラスでこの「食べる」メソッドをオーバーライドしてみます。
class Dog extends Animal { @Override void eat() { System.out.println("The dog eats dog food."); } }
この例では、「犬」クラスは「動物」クラスの「食べる」メソッドをオーバーライドし、犬がドッグフードを食べるという動作を表現しています。
実際にこの「犬」クラスを使ってみると、以下のようになります。
public class Main { public static void main(String[] args) { Dog myDog = new Dog(); myDog.eat(); // オーバーライドした「食べる」メソッドを呼び出す } } // 出力結果 // The dog eats dog food.
このように、親クラスの「食べる」メソッドが「This animal eats.」というメッセージを出力するのに対し、子クラスでオーバーライドされた「食べる」メソッドは「The dog eats dog food.」というメッセージを出力します。
メソッドのオーバーライドを利用することで、親クラスの基本的な動作を引き継ぎつつ、子クラスで特定の動作をカスタマイズすることができます。これにより、プログラムの柔軟性が高まり、特定の状況に応じた動作を簡単に実装することができます。
オーバーライドは、オブジェクト指向プログラミングにおける多態性(ポリモーフィズム)を実現するための重要な手法です。より詳しく学習したい方はこちら:Javaオーバーライドの基本

親クラスのメソッドを再定義することで、コードの再利用性を維持しつつ、必要に応じて動作を変更できるため、非常に強力な機能と言えます。
superキーワードの使用
super
キーワードは、親クラスのメソッドやコンストラクタにアクセスするために使用されます。→つまり、子クラス内で親クラスのメソッドを呼び出したり、親クラスのコンストラクタを明示的に呼び出すことができます。
具体的な例を見てみましょう。まず、いつも通り親クラスに「食べる」メソッドを定義。
class Animal { void eat() { System.out.println("This animal eats."); } }
次に、子クラスで「食べる」メソッドをオーバーライドし、その中でsuper
キーワードを使って親クラスの「食べる」メソッドを呼び出します。
class Dog extends Animal { @Override void eat() { super.eat(); // 親クラスの「食べる」メソッドを呼び出す System.out.println("The dog eats dog food."); // 子クラス独自の処理を追加 } }
この例では、「犬」クラスの「食べる」メソッドの中でsuper.eat()
を呼び出しています。この結果、親クラスの「食べる」メソッドが実行され、その後に子クラス独自の処理が続けて実行されることになります。

実際にこの「犬」クラスを使ってみると、以下の出力が得られます。
public class Main { public static void main(String[] args) { Dog myDog = new Dog(); myDog.eat(); // オーバーライドした「食べる」メソッドを呼び出す } } // 出力結果 // This animal eats. // The dog eats dog food.
このサンプルでは、親クラスの「食べる」メソッドが「This animal eats.」というメッセージを出力し、その後に子クラスの「食べる」メソッドが「The dog eats dog food.」というメッセージを出力します。このように、super
キーワードを使用することで、親クラスのメソッドを呼び出し、その後に子クラスの追加処理を行うことができます。
さらに、super
キーワードは親クラスのコンストラクタ(参考 コンストラクタとは?)を呼び出すためにも使用される場合もあります。親クラスのコンストラクタを呼び出すことで、親クラスの初期化処理を行うことができます。
class Animal { Animal() { System.out.println("An animal is created."); } } class Dog extends Animal { Dog() { super(); // 親クラスのコンストラクタを呼び出す System.out.println("A dog is created."); } }
この例では、親クラスのコンストラクタが先に実行され、その後に子クラスのコンストラクタが実行されます。実際にこのクラスを使ってみると、以下のようになります。
public class Main { public static void main(String[] args) { Dog myDog = new Dog(); // コンストラクタを呼び出す } } // 出力結果 // An animal is created. // A dog is created.
このように、super
キーワードを使うことで、親クラスの初期化処理を確実に行い、子クラスの初期化処理を続けて行うこともできるのがポイント。super
キーワードは、親クラスと子クラスの連携をスムーズにし、コードの再利用性と保守性を向上させる重要な機能です。
継承の制限
Javaの継承にはいくつかの制限があります。

ここでは、主な制限として、final
キーワードの使用と、多重継承の禁止について説明します。
finalキーワード
final
キーワードは、クラスやメソッドを継承したりオーバーライドしたりすることを禁止するために利用されます。以下のようにfinal
を利用することで継承することを禁ずるため、そのクラスを親クラスとして新しいクラスを作ることはできなくなります。
final class Animal { // このクラスは継承できません } class Dog extends Animal { // エラー:Animalクラスはfinalなので継承できません }
同様にメソッドについても、final
を利用することでオーバーライドすることができなくなります。→子クラスでそのメソッドの動作を変更することはできません。
class Animal { final void eat() { System.out.println("This animal eats."); } } class Dog extends Animal { @Override void eat() { // エラー:eatメソッドはfinalなのでオーバーライドできません System.out.println("The dog eats dog food."); } }
これにより、重要なクラスやメソッドの動作が変更されないようにすることができます。
多重継承の禁止
Javaではクラスの多重継承が禁止されています。多重継承とは、1つのクラスが複数のクラスを同時に継承することです。多重継承を許可すると、同じメソッドやフィールドが複数の親クラスに存在する場合にどれを使うべきかが曖昧になるため、Javaではこれを避けるために多重継承を禁止しています。
class Animal { void eat() { System.out.println("This animal eats."); } } class Pet { void play() { System.out.println("This pet plays."); } } class Dog extends Animal, Pet { // エラー:Javaは多重継承をサポートしません }
この制限を回避するために、Javaではインターフェースを使用することができます。インターフェースを用いることで、多重継承に似た効果を実現することができます。(参考 Javaのインターフェースとは?)
interface Eatable { void eat(); } interface Playable { void play(); } class Dog implements Eatable, Playable { @Override public void eat() { System.out.println("The dog eats dog food."); } @Override public void play() { System.out.println("The dog plays fetch."); } }
このように、implements
キーワードを使って複数のインターフェースを実装することで、多重継承のような効果を得ることができます。インターフェースはメソッドの宣言のみを持ち、実装は各クラスが行うため、メソッドの競合が発生しません。

これらの制限を理解し、適切に活用することで、Javaの継承機能を正しく使いこなすことができます。final
キーワードとインターフェースを活用して、安全で効率的なプログラムを作成しましょう。
何が継承されて、何が継承されないのか
継承で「何が引き継がれて、何が引き継がれないのか」を整理すると、次のようになります。
■ 継承されるもの
- 親クラスのメソッドやフィールド(変数)
public
やprotected
で定義されているメソッドやフィールドは、子クラスで利用可能です。private
なフィールドやメソッドも、実際のメモリ領域としては親クラス由来のものを持ちますが、子クラスからは直接アクセスできない(見えない)点に注意が必要です。
- 親クラスが持つクラス(static)メンバー
- 静的メソッドや静的フィールド(staticなメンバー)も継承の対象にはなりますが、オーバーライド(上書き)することはできません。
- 「継承されている」というより、クラスローダーを通じて同じ名前空間に含まれているイメージが近いです。
■ 継承されない(直接引き継がれない)もの
- コンストラクタ(Constructor)
- Javaのコンストラクタは子クラスに引き継がれません。
- 子クラスのコンストラクタが呼ばれるときに、
super(...)
で親クラスのコンストラクタを指定することで、親クラスの初期化部分を呼び出すだけです。
- 親クラスのprivateメンバーへの直接アクセス権
- 前述のとおり、privateなフィールドやメソッドのメモリ領域は受け継ぐものの、子クラスからは直接アクセスできません。
- 必要であれば、親クラスに
protected
やpublic
なメソッドを設けて、子クラスからの利用を可能にします(“ゲッター/セッター”など)。
このように、継承される部分・されない部分を踏まえて、親クラスを変更するときの影響や、子クラスで意図しない動きが起きないか、注意深く設計することが大切です。
継承関係にあるクラスで同名の変数をそれぞれ定義している場合の挙動
継承関係にあるクラスで、親クラスと子クラスが同名の変数(フィールド)をそれぞれ定義している場合、子クラスのフィールドは「親クラスのフィールドを隠す(shadowing/hiding)」動作をします。これはメソッドのオーバーライド(override)とは異なり、変数の場合は「動的ディスパッチ(動的バインディング)」ではなく「静的に(コンパイル時に)どのクラスの変数を使うかが決定される」という点に注意が必要です。
例えば以下のようなコードがあるとします。
class Parent { String name = "ParentName"; } class Child extends Parent { String name = "ChildName"; }
- 親クラス
Parent
にname
フィールドがあり、子クラスChild
にも同じ名前のname
フィールドが定義されている。 - このとき、子クラス側の
name
フィールドは親クラスのname
フィールドを隠す形になる。
使用例を見ると分かりやすいです:
Child child = new Child(); System.out.println(child.name); // ChildName が表示される System.out.println(((Parent) child).name); // ParentName が表示される
同名フィールドは上記のように「隠蔽」の関係になるため、通常は同名変数の再定義は推奨されません。開発者が混乱を招くおそれがあり、またメソッドのようにオーバーライドによる動的ディスパッチは行われないため、意図しない変数が参照されてしまう可能性があるからです。もしどうしても継承で同名変数を扱いたい場合には、super.name
を使って親クラスのフィールドを明示的に参照するなど、記述を明確に分けて使いましょう。
継承とポリモーフィズムの基本
Javaの継承は、ポリモーフィズム(多態性)を実現します。これにより、あるクラスAを継承したクラスBのインスタンスは、A型の変数で扱うことができ、実際にはBのオーバーライドされたメソッドが呼び出されるなど、柔軟なプログラム設計が可能になります。

以下は、スーパークラスAとそれを継承するサブクラスBのサンプルコードです。
// スーパークラス A class A { public void doSomething() { System.out.println("Aの処理"); } } // サブクラス B class B extends A { // Aのメソッドをオーバーライド @Override public void doSomething() { System.out.println("Bの処理"); } // B独自のメソッド public void uniqueMethod() { System.out.println("B特有の処理"); } } public class Main { public static void main(String[] args) { // BのインスタンスをA型で扱う A a = new B(); a.doSomething(); // 結果: "Bの処理" (オーバーライドされたメソッドが呼ばれる) // A型の変数からはB独自のメソッドは直接呼べない // a.uniqueMethod(); // コンパイルエラーになる // Bのメソッドを呼び出すにはキャストが必要 if (a instanceof B) { B b = (B) a; b.uniqueMethod(); // 結果: "B特有の処理" } } }
- ポリモーフィズムにより、スーパークラス型の変数でサブクラスのインスタンスを扱えます。
- オーバーライドされたメソッドは、実際のオブジェクトの型に基づいて実行されるため、柔軟な動作が可能です。
- B独自のメソッドを利用する際は、必ず安全なキャストを行う必要があります。
- 参照型と実体型の違いを理解することで、より堅牢なコード設計が実現できます。
注意点
- キャストの必要性
A型の変数は、B独自のメソッドやフィールドに直接アクセスできません。B特有の機能を利用するためには、明示的なキャストが必要です。キャストの際には、instanceof
演算子を使用して実際の型を確認することが推奨されます。 - ダウンキャストのリスク
A型の変数にB以外のサブクラスや異なる型のオブジェクトが格納されている場合、無理なキャストはClassCastException
を引き起こす可能性があります。安全なキャストのために、必ずinstanceof
でチェックしてください。 - メソッドのオーバーライド
サブクラスBがスーパークラスAのメソッドをオーバーライドしている場合、A型の変数でも実際のオブジェクトがBであれば、Bのオーバーライドされたメソッドが呼び出されます。これにより、動的バインディングが実現され、柔軟な振る舞いが可能となります。 - 参照型と実体型の違い
コンパイル時に用いる型(参照型)と、実行時のオブジェクトの型(実体型)は異なる場合があります。メソッドの呼び出しは実体型に基づいて行われますが、参照型で定義されたメソッドやフィールドにしかアクセスできないため、これらの違いを意識した設計が重要です。
このように、Javaの継承とポリモーフィズムはコードの再利用性と拡張性を向上させる一方で、キャストや型チェックといった注意点もあるため、実装時には十分な配慮が必要です。
アップキャスト(upcast)とダウンキャスト(downcast)
アップキャスト(Upcasting)
アップキャストとは、「サブクラス(子クラス)のインスタンスを、スーパークラス(親クラス)として扱う」キャストのことです。
Java では継承関係にある型同士で、サブクラス → スーパークラス 方向の変換は自動で行われます。これを“アップキャスト”と呼ぶことがあります。
参考 キャストとは?
なぜアップキャストが必要なのか
class Animal { public void makeSound() { System.out.println("Some sound..."); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("Bow wow!"); } public void wagTail() { System.out.println("Tail wagging!"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); // Dog型インスタンスを作成 Animal animal = dog; // 自動的にアップキャストされる animal.makeSound(); // Dogの実装が呼ばれる("Bow wow!"を出力) } }
Dog dog = new Dog();
でDog
型のインスタンスを生成した後、Animal animal = dog;
とすると、Javaが自動的にDog
→Animal
へキャストする(アップキャスト)。animal.makeSound();
は実行時にDog
のmakeSound()
を呼び出します(動的バインディング)。- ただし、
animal
は型としてはAnimal
とみなされるため、Dog
でしか定義されていないwagTail()
メソッドは直接呼び出せません。
ダウンキャスト(Downcasting)とは
ダウンキャストとは、「スーパークラス型で参照しているインスタンスを、サブクラス型として扱い直す」キャストのことです。
アップキャストと異なり、自動では行われず明示的にキャストを書く必要があります。さらに、実際に指しているインスタンスが本当にサブクラスのオブジェクトでなければ、ClassCastException
が発生します。
なぜダウンキャストが必要なのか
- スーパークラス型の変数で参照されているインスタンスが、特定のサブクラスのオブジェクトだとわかっている場合、そのサブクラス独自のメソッドやフィールドを呼び出したいことがあります。
- その際、スーパークラス型の変数をサブクラス型として使うためにキャスト(ダウンキャスト)を行います。
class Animal { public void makeSound() { System.out.println("Some sound..."); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("Bow wow!"); } public void wagTail() { System.out.println("Tail wagging!"); } } public class Main { public static void main(String[] args) { Animal animal = new Dog(); // アップキャストされている(実態はDog) animal.makeSound(); // Bow wow! // animal変数をDog型として利用したい Dog dog = (Dog) animal; // ダウンキャスト(明示的にキャストが必要) dog.wagTail(); // Dog固有のメソッドを呼び出し } }
Animal animal = new Dog();
の時点でインスタンスはDog
ですが、型はAnimal
で参照しています。Dog dog = (Dog) animal;
によって、animal
が指している実体をDog
型へ変換(ダウンキャスト)しています。- このとき、もし
animal
が本当はCat
など別のクラスのインスタンスだった場合、実行時にClassCastException
となります。
アップキャスト (Upcasting)
- サブクラスのインスタンスをスーパークラス型の変数で扱うこと。
- キャストの明示は不要。自動的に行われる。
- スーパークラスにあるメソッドや変数しか呼び出せなくなるが、実装は実際のオブジェクト(サブクラス)側が呼び出される(多態性)。
ダウンキャスト (Downcasting)
- スーパークラス型の変数が指しているサブクラスのインスタンスを、サブクラス型に戻して使うこと。
- 明示的にキャストを書く必要がある。
- 実際に指しているオブジェクトの型がサブクラスでなければ
ClassCastException
が起きるため、instanceof
を使って安全確認するのが一般的。