Javaの継承とは、既存のクラスの機能を引き継いで新しいクラスを作る仕組みで、extendsキーワードで書きます。共通の処理を親クラスにまとめ、子クラスでは違う部分だけを書けるようになります。
私も10年以上前にJavaを学び始めたころは、継承の考え方をつかむのに苦労しました。この記事では、extendsとsuperの使い方、オーバーライド、インターフェースとの違い、実務で継承を使うときの設計の考え方まで、サンプルコードで順に説明します。
この記事は、Java入門ロードマップの「STEP4|オブジェクト指向」に対応しています。全体の学習順は次の記事で確認できます。
PARTS LIST /目次
Javaの継承とは?基本の考え方をわかりやすく解説
Javaにおける継承は、オブジェクト指向プログラミングの中核をなす重要な仕組みの1つです。
一言でいうと、あるクラスが持つ性質(フィールドやメソッド)を、別のクラスが受け継ぐ機能を指します。
この仕組みを理解することで、コードの再利用性が高まり、より効率的で保守性の高いプログラムを組めるようになります。
継承の定義と目的
継承の主な目的は、コードの重複をなくし、プログラムの構造を整理する点にあります。
たとえば、ゲームに登場する「犬」クラスと「猫」クラスを作るとします。「歩く」「食べる」といった動作は、どちらにも共通する機能です。
継承を使わない場合、それぞれのクラスに同じ内容のコードを書く必要があり、非効率ですし、修正があった場合は2箇所を直さなければなりません。
そこで、これらの共通機能をまとめた「動物」クラスを作り、犬クラスと猫クラスが「動物」クラスを継承します。
そうすれば、「歩く」「食べる」といったコードは1箇所にまとめられ、プログラム全体がすっきりと見やすくなります。
クラスとスーパークラス(親クラス・子クラス)の関係
継承を語るうえで欠かせないのが、クラス間の関係を示す2つの用語です。
- スーパークラス(親クラス): 継承元となるクラス。機能を受け継がせる側。基底クラスとも呼ばれます。
- サブクラス(子クラス): 継承先となるクラス。機能を受け継ぐ側。派生クラスとも呼ばれます。
先ほどの例では、「動物」がスーパークラス(親クラス)であり、「犬」と「猫」がサブクラス(子クラス)にあたります。
サブクラスは、スーパークラスの機能(publicやprotectedで定義されたフィールドやメソッド)をまるで自分のもののように利用できます。
さらに、サブクラス独自の機能を追加することも可能です。
protectedアクセス修飾子と継承の関係
privateで定義したフィールドやメソッドは、サブクラスであっても直接アクセスできません。
継承前提のクラス設計では、サブクラスに使わせたいメンバーをprivateではなくprotectedにします(フィールドはprivateのままにして、protectedのメソッド経由で使わせるとより安全です)。
private: 同じクラス内からのみアクセス可能。サブクラスからも直接アクセスできません。protected: 同じパッケージ内、および別パッケージのサブクラスからアクセス可能です。
そのため、サブクラスに直接利用させたいフィールドやメソッドはprotectedで定義します。
外部に一切公開したくない内部実装だけをprivateにする、という使い分けが基本です。
継承を使うメリット・デメリット
継承は強力な機能ですが、良い面ばかりではありません。メリットとデメリットを正しく理解して使いこなしましょう。
メリット
- コードの再利用性が向上する: 共通の機能をスーパークラスにまとめることで、同じコードを何度も書く必要がなくなる。
- 保守性が高まる: 共通機能の修正が必要になった場合、スーパークラスの1箇所を修正するだけで、そのメソッドをオーバーライドしていないサブクラスすべてに修正が反映される。
- 多態性(ポリモーフィズム)を実現できる: スーパークラスの型としてサブクラスのオブジェクトを扱えるようになり、柔軟で拡張性の高いプログラムを書きやすくなる。
デメリット
- クラス間の結合度が強くなる(密結合): 親クラスの仕様変更が、意図せず子クラスに影響を与えてしまう可能性がある。親子関係が強固になりすぎるため、柔軟性が失われる場合がある。
- 構造が複雑になる: 継承関係が何階層にも深くなると、クラスの全体像を把握するのが難しくなり、かえってプログラムが複雑化する恐れがある。
Animalname / eat() など共通部分をまとめるDogrun() を追加cry() をオーバーライド(「ワン!」)eat() はそのまま継承Catcry() をオーバーライド(「ニャー」)eat() はそのまま継承Javaで継承を使う基本構文【extends】
それでは、実際にJavaで継承を利用するための具体的な構文を見ていきましょう。キーワードはextendsです。
基本構文の書き方と使い方
クラスを継承するには、サブクラス(子クラス)の定義時にextendsキーワードに続けてスーパークラス(親クラス)を指定します。
class サブクラス名 extends スーパークラス名 {
// クラスの定義
}
たったこれだけで、サブクラスはスーパークラスの機能を受け継ぐことができます。
superの使い方|親クラスの呼び出し方法
サブクラスでは、スーパークラスのメソッドやコンストラクタを明示的に呼び出したい場合があります。その際に活躍するのがsuperキーワードです。
super.メソッド名(): スーパークラスのメソッドを呼び出します。super(): スーパークラスのコンストラクタを呼び出します。
特に、サブクラスのコンストラクタの先頭でsuper()を呼び出し、スーパークラスの初期化処理を行うのは定石パターンです。
class Animal {
String name;
Animal(String name) {
this.name = name;
System.out.println("Animalクラスのコンストラクタが呼ばれました");
}
void cry() {
System.out.println("動物が鳴きます");
}
}
class Dog extends Animal {
Dog(String name) {
// スーパークラスのコンストラクタを呼び出す
super(name);
System.out.println("Dogクラスのコンストラクタが呼ばれました");
}
}
この例では、Dogクラスのインスタンスを生成すると、まずsuper(name)によってAnimalクラスのコンストラクタが実行され、その後にDogクラスのコンストラクタ内の処理が実行されます。
メソッドオーバーライド(@Override)の仕組み
継承の大きな特徴の1つにメソッドのオーバーライドがあります。
オーバーライドとは、スーパークラスで定義されたメソッドを、サブクラスで再定義(上書き)する仕組みです。
たとえば、「動物」クラスのcry()メソッドは「動物が鳴きます」と出力しますが、「犬」クラスでは「ワン!」と鳴いてほしいですよね。
このような場合にオーバーライドを使います。
メソッドをオーバーライドする際は、メソッドの前に@Overrideというアノテーションを付けるのが一般的です。
これは、コンパイラに対して「このメソッドはオーバーライドしています」と伝えるための目印です。
もしメソッド名や引数の型を間違えていた場合、コンパイラがエラーを教えてくれるため、バグの防止につながります。
class Dog extends Animal {
Dog(String name) {
super(name);
}
// Animalクラスのcryメソッドをオーバーライド
@Override
void cry() {
System.out.println(this.name + "は「ワン!」と鳴きます");
}
}
オーバーライドのメリット
オーバーライドを使う最大のメリットは、共通の仕組みを保ちながら、クラスごとに異なる振る舞いを持たせられる点です。
たとえば、オーバーライドを使わずに犬と猫で異なる鳴き声を実現しようとすると、次のような問題が起きます。
問題
- クラスごとに別名のメソッド(dogCry()、catCry()など)を作る必要がある
- 呼び出し側で「このクラスならこのメソッド」と分岐処理を書く必要がある
新しい動物が増えるたびに、呼び出し側のコードも修正しなければなりません。
オーバーライドを使えば、どのクラスでもcry()という同じメソッド名で呼び出せます。
呼び出し側のコードを一切変えずに、新しい動物クラスを追加できるのです。これが後述する多態性(ポリモーフィズム)の土台にもなります。
実際に動かして理解する継承のコード例
ここからは、具体的なコード例を通して、Java継承がどのように機能するのかをさらに深く見ていきます。
Animalクラスを継承したDogクラスの例
これまで説明してきたAnimalクラスとDogクラスの完成形と、それを実行するコードを見てみましょう。
// スーパークラス
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println(this.name + "は食事をします");
}
void cry() {
System.out.println("動物が鳴きます");
}
}
// サブクラス
class Dog extends Animal {
Dog(String name) {
super(name); // 親クラスのコンストラクタを呼び出す
}
// 独自のメソッドを追加
void run() {
System.out.println(this.name + "は走り回ります");
}
// 親クラスのメソッドをオーバーライド
@Override
void cry() {
System.out.println(this.name + "は「ワン!」と鳴きます");
}
}
// 実行クラス
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog("ポチ");
myDog.eat(); // 親クラスのメソッドを呼び出し
myDog.run(); // 子クラス独自のメソッドを呼び出し
myDog.cry(); // オーバーライドしたメソッドを呼び出し
}
}
実行結果:
ポチは食事をします
ポチは走り回ります
ポチは「ワン!」と鳴きます
DogクラスはAnimalクラスを継承しているので、定義していないeat()メソッドを呼び出せています。
また、Dogクラス独自に定義したrun()メソッドも使えますし、cry()メソッドはオーバーライドした内容で実行されているのがわかります。
共通処理をまとめる設計パターンの実例
実務では、データベースへの接続情報やログ出力機能など、多くのクラスで共通して利用したい処理が出てきます。
このような共通処理をスーパークラスにまとめて、各クラスがそれを継承する設計はよく使われます。
// 共通機能をまとめたスーパークラス
abstract class BaseService {
void connectDatabase() {
System.out.println("データベースに接続しました");
}
void log(String message) {
System.out.println("[LOG] " + message);
}
}
// ユーザー情報を扱うクラス
class UserService extends BaseService {
void findUser(int userId) {
connectDatabase(); // 親クラスのメソッドを利用
log("ユーザーID: " + userId + " を検索します");
// 検索処理...
}
}
// 商品情報を扱うクラス
class ProductService extends BaseService {
void findProduct(int productId) {
connectDatabase(); // 親クラスのメソッドを利用
log("商品ID: " + productId + " を検索します");
// 検索処理...
}
}
UserServiceとProductServiceは、BaseServiceを継承することで、データベース接続やログ出力の機能を自分で実装することなく利用できます。
オーバーライドと多態性(ポリモーフィズム)の関係
継承とオーバーライドを学ぶと、オブジェクト指向のもう1つの重要な柱である多態性(ポリモーフィズム)への扉が開かれます。
多態性とは、同じ型の変数やメソッド呼び出しであっても、実際のインスタンスの種類によって異なる振る舞いをする性質です。
public class Main {
public static void main(String[] args) {
// 親クラスの型で子クラスのインスタンスを扱う
Animal myCat = new Cat("タマ"); // CatクラスもAnimalを継承していると仮定
Animal myDog = new Dog("ポチ");
myCat.cry(); // Catクラスでオーバーライドされたメソッドが呼ばれる
myDog.cry(); // Dogクラスでオーバーライドされたメソッドが呼ばれる
}
}
実行結果:
タマは「ニャー」と鳴きます
ポチは「ワン!」と鳴きます
myCatもmyDogも同じAnimal型として宣言されています。
しかしcry()メソッドを呼び出すと、それぞれのインスタンスの実際のクラス(CatやDog)でオーバーライドされたメソッドが実行されます。
1つのインターフェース(ここではcry()メソッド)で、異なる実装を呼び分けられるのが多態性の強力な点です。
myCatmyCat.cry()Cat「タマ」myDogmyDog.cry()Dog「ポチ」cry()。動くメソッドは中の実体(Cat/Dog)で決まります。実務では、数千行のif-elseが連なる「モンスターメソッド」の保守を経験したことがあります。
分岐を1つ追加するたびに全条件を確認する必要があり、修正のたびに冷や汗をかく状態でした。
この処理をポリモーフィズムで設計し直したところ、追加・修正にかかるコストが約1/5にまで下がりました。
分岐が増え続けて手に負えなくなってきたときこそ、継承と多態性を使った設計への切り替えを検討するタイミングです。
継承とインターフェース・抽象クラスの違い
Javaには、継承と似た目的で使われる機能として「インターフェース」と「抽象クラス」があります。
これらは混同しやすいため、違いをしっかりと理解しておくことが重要です。
extendsとインターフェースの使い分け
最大の違いは、Javaではクラスの継承(extends)は1つしかできないのに対し、インターフェースの実装(implements)は複数できる点です。
その他の違いは以下の通りです。
抽象クラスの特徴と継承の関係
抽象クラスは、インスタンス化できない、不完全なクラスです。抽象メソッド(処理内容が定義されていないメソッド)を持つことができます。
抽象クラスは、継承されることを前提としています。
サブクラスで抽象メソッドをオーバーライドし、具体的な処理を実装することで、初めて完全なクラスとして機能します。
インターフェースと似ていますが、抽象クラスは、インスタンスごとに値を保持するフィールド(状態)を持てる点で異なります(インターフェースに書けるのは定数だけです)。
共通の機能や状態を持ちつつ、一部の処理だけをサブクラスに強制したい場合に利用されます。
コンポジションで継承を避けるケース
プログラミングの世界には、「継承より委譲(コンポジション)を」という有名な設計原則があります。
継承は親子関係が強固(密結合)になるというデメリットがありました。
そこで、あるクラスの機能を利用したい場合に、継承するのではなく、そのクラスのインスタンスをフィールドとして内部に持つ方法がコンポジションです。
class Engine {
void start() {
System.out.println("エンジンが始動しました");
}
}
class Car {
// 継承ではなく、Engineクラスのインスタンスを内部に持つ
private Engine engine;
Car() {
this.engine = new Engine();
}
void start() {
// Engineの機能を利用する(委譲)
this.engine.start();
System.out.println("車が発進します");
}
}
この設計では、CarとEngineの関係が継承よりも疎結合になります。
これにより、Engineクラスの変更がCarクラスに与える影響を最小限に抑えられ、より柔軟で再利用性の高い設計が実現できます。
finalキーワードで継承を禁止する方法
クラスやメソッドにfinalキーワードを付けると、継承やオーバーライドを禁止できます。意図しない拡張を防ぎたい場合に使う機能です。
// クラス自体の継承を禁止する
final class Animal {
void cry() {
System.out.println("動物が鳴きます");
}
}
// class Dog extends Animal {} // コンパイルエラーになる
class Base {
// このメソッドはサブクラスでオーバーライドできない
final void identify() {
System.out.println("Baseクラスです");
}
}
final classはクラス全体の継承を禁止し、finalメソッドはそのメソッドだけオーバーライドを禁止します。
不変クラス(一度作成したら状態が変わらないクラス)や、意図しない拡張によるバグを防ぎたい基盤クラスでよく使われます。
sealedクラスで継承先を限定する方法(Java 17以降)
finalが継承を完全に禁止するのに対し、Java 17で標準機能として導入されたsealedは、継承を許可するクラスを明示的に限定できる仕組みです。
permits句で許可するサブクラスを指定します。
// Shapeを継承できるのはCircleとSquareのみに限定
sealed class Shape permits Circle, Square {
}
final class Circle extends Shape {
}
final class Square extends Shape {
}
// class Triangle extends Shape {} // permitsに無いため継承不可
許可されたサブクラス側は、final(それ以上継承させない)・sealed(さらに限定して継承させる)・non-sealed(制限を解除して自由に継承させる)のいずれかを必ず指定する必要があります。
「完全に禁止(final)」と「無制限(通常の継承)」の中間として、設計者の意図した範囲だけ拡張を許す用途に使われます。
Javaでクラスの多重継承はできない?その理由と代替案
Javaの継承を学んでいると、「複数のクラスを一度に継承できないのだろうか?」という疑問が浮かぶかもしれません。
結論からお伝えすると、Javaではクラスの多重継承はサポートされていません。 1つのクラスが継承できる親クラスは、常に1つだけです。
これはJavaの言語仕様における重要な制約であり、プログラムの複雑化を防ぐための意図的な設計になります。
この章では、なぜJavaが多重継承を禁止しているのか、そしてその代替策について分かりやすく解説します。
多重継承禁止の理由【ダイヤモンド問題】
Javaがクラスの多重継承を許可しない主な理由の一つは、「ダイヤモンド問題」と呼ばれる状態を避けるためです。
ダイヤモンド問題とは、あるクラスが、同じスーパークラスを継承する2つの異なるクラスを同時に継承しようとしたときに発生するあいまいさの問題を指します。
具体的な例で見ていきましょう。
仮に、Javaで多重継承が可能だったとします。
Characterというスーパークラスがあり、attack()メソッドを持つWarriorクラスとWizardクラスが、それぞれCharacterクラスを継承します。そして、両方のクラスでattack()メソッドを独自にオーバーライドする(戦士は剣で攻撃、魔法使いは魔法で攻撃)。- ここで、
MagicKnightという新しいクラスが、WarriorとWizardの両方を継承しようとする
// ※これはJavaではコンパイルエラーになるコードです
class Character {
void attack() {
System.out.println("キャラクターが攻撃する");
}
}
class Warrior extends Character {
@Override
void attack() {
System.out.println("剣で攻撃する!");
}
}
class Wizard extends Character {
@Override
void attack() {
System.out.println("魔法で攻撃する!");
}
}
// WarriorとWizardを多重継承しようとすると…
class MagicKnight extends Warrior, Wizard { // このような書き方はできません
// ここで attack() を呼び出すと、
// Warriorのattack()とWizardのattack()の
// どちらを呼べばいいのか分からない!
}
このとき、MagicKnightクラスのインスタンスからattack()メソッドを呼び出すとどうなるでしょうか。
コンパイラはWarriorのattack()とWizardのattack()のどちらを実行すればよいのか判断できません。
継承関係がひし形(ダイヤモンド)のようになり、メソッドの呼び出し元が特定できなくなる問題をダイヤモンド問題と呼びます。
この問題を言語仕様レベルで防ぐために、Javaはクラスの多重継承を禁止しているのです。
Characterattack() を持つWarriorattack() →「剣で攻撃する!」Wizardattack() →「魔法で攻撃する!」MagicKnightattack() は Warrior と Wizard のどちらを引き継ぐのか決められないextends に書けるクラスは1つだけです。インターフェースによる多重継承(多重実装)
Javaではクラスの多重継承はできませんが、インターフェースを使うことで多重継承と似た機能を実現できます。 クラスは複数のインターフェースを実装(implements)することが可能です。
インターフェースは、メソッドのシグネチャ(名前、引数、戻り値の型)だけを定義し、原則として具体的な実装は持ちません(Java 8以降のdefaultメソッドとstaticメソッド、Java 9以降のprivateメソッドを除く)。
そのため、複数のインターフェースを実装しても、通常はどのメソッドを呼び出すかというあいまいさは発生しない仕組みです。
実装は基本的に、実装したクラス側で行われるからです。ただし、2つのインターフェースが同じシグネチャのdefaultメソッドを持つ場合は、実装クラスでそのメソッドをオーバーライドして解決しないとコンパイルエラーになります。
先ほどのMagicKnightの例を、インターフェースを使って再設計してみましょう。
// 攻撃できる、という「機能」をインターフェースで定義
interface SwordAttack {
void attackWithSword();
}
interface MagicAttack {
void attackWithMagic();
}
// 機能(インターフェース)をクラスに実装する
class MagicKnight implements SwordAttack, MagicAttack {
@Override
public void attackWithSword() {
System.out.println("剣で素早く斬りつける!");
}
@Override
public void attackWithMagic() {
System.out.println("炎の魔法を唱える!");
}
// 両方の技を使う独自のメソッドも作れる
public void combinedAttack() {
attackWithSword();
attackWithMagic();
}
}
public class Main {
public static void main(String[] args) {
MagicKnight knight = new MagicKnight();
knight.attackWithSword();
knight.attackWithMagic();
knight.combinedAttack();
}
}
実行結果:
剣で素早く斬りつける!
炎の魔法を唱える!
剣で素早く斬りつける!
炎の魔法を唱える!
MagicKnightクラスはSwordAttackとMagicAttackという2つの「機能」を実装することで、「剣でも魔法でも攻撃できる」という性質を持ちます。
これは、クラスの継承が「is-a(~の一種である)」の関係を表すのに対し、インターフェースは「can-do(~ができる)」という役割や能力を表すという考え方に基づいています。
よくある質問
Q1. Javaのextendsとは何ですか?
A1. extendsは、あるクラス(スーパークラス)の機能を別のクラス(サブクラス)に引き継がせるためのキーワードです。「class 子クラス extends 親クラス」という構文で使います。
Q2. extendsとimplementsの違いは何ですか?
A2. extendsはクラスの継承(1つしかできない)に使い、implementsはインターフェースの実装(複数できる)に使います。extendsは「is-a(〜の一種である)」、implementsは「can-do(〜ができる)」の関係を表します。
Q3. Javaでクラスを複数継承(多重継承)できますか?
A3. できません。Javaはダイヤモンド問題などの複雑さを避けるため、クラスの多重継承を禁止しています。複数の機能を組み合わせたい場合は、複数のインターフェースをimplementsで実装します。
Q4. extendsを使うときsuperは必須ですか?
A4. 親クラスに引数なしのコンストラクタがある場合は省略できます。ただし、親クラスに引数付きコンストラクタしか無い場合は、子クラスのコンストラクタ先頭でsuper(引数)のように引数を渡して明示的に呼び出す必要があります。
Q5. finalクラスをextendsで継承できますか?
A5. できません。finalが付与されたクラスは継承が禁止されています。継承を許可する範囲を限定したい場合は、Java 17で導入されたsealedクラスとpermits句を使う方法もあります。
Q6. 継承とコンポジション(委譲)はどちらを使うべきですか?
A6. クラス間に明確な「is-a」関係があり、共通の基盤を共有するメリットが大きい場合は継承を、それ以外の「機能だけを利用したい」ケースではコンポジションを選ぶのが基本方針です。コンポジションのほうが結合度が低く、柔軟な設計を維持しやすい傾向があります。
まとめ:継承を正しく使いこなすためのポイント
最後に、Javaの継承を使いこなすための重要なポイントをまとめます。
継承を乱用しない設計のコツ
実務の現場では、継承そのものに否定的な意見も少なくありません。
理由の1つは、親クラスが何段にも積み重なっていくと、あるメソッドの処理を追うために親・祖父母・曾祖父母クラスとファイルを何度も行き来する必要が出てくるためです。
継承は強力なツールですが、万能薬ではありません。使うべき場面を見極めることが肝心です。
設計を始める前に、本当に「is-a」の関係が成り立つのかを自問自答しましょう。
安易な継承は、将来的にプログラムを複雑にし、保守性を低下させる原因となります。
実務で役立つ継承設計の考え方
実務では、まずインターフェースやコンポジションで実現できないかを検討する癖をつけるのがおすすめです。
これらのほうが、クラス間の結合度を低く保ち、柔軟な設計を維持しやすいためです。
そのうえで、クラス間に明確な「is-a」関係があり、共通の基盤を共有するメリットが大きい場合にのみ、継承を選択肢に入れると良いでしょう。
| REV. | DATE | 内容 |
|---|---|---|
| 5 | 2026-10-16 | 図解を描き直し(図面のデザインにそろえた) |
| 4 | 2026-10-14 | 雰囲気だけの挿絵を外した |
| 3 | 2026-10-04 | 記載内容の事実確認と修正 |
| 2 | 2026-09-03 | 2026年9月時点の情報で全体を見直し(古い記述を1件更新) |
| 1 | 2025-10-13 | 初版 |






