Java道|Javaの継承の基本と使い方

親の力を受け継ぎ、子クラスで自分だけの技を加える。
Javaの継承は、共通部分をまとめて、クラスを強く育てるための仕組みです。

Javaのオブジェクト指向では、すでに作ったクラスを土台にして、新しいクラスを作ることができます。
この仕組みを 継承 といいます。

継承を使うと、似たような性質を持つクラスを毎回最初から作らなくてよくなります。
共通している情報や処理は親クラスにまとめておき、子クラスでは必要な特徴だけを追加できます。

鬼滅の刃風にたとえると、まず鬼殺隊士という共通の土台があります。
鬼殺隊士であれば、名前を持ち、階級を持ち、任務に出ることができます。

そこから、柱のような特別な隊士を作る場合、鬼殺隊士としての基本はそのまま受け継ぎます。
そのうえで、担当区域や指揮能力のような追加要素を持たせるイメージです。

つまり継承は、

考え方内容
共通部分親クラスにまとめる
個別の特徴子クラスに追加する
目的コードの重複を減らし、拡張しやすくする

という考え方です。今回の記事では、継承、スーパークラス、サブクラス、extends、クラスの拡張について、鬼滅の刃風の世界観に置きかえて解説します。

継承とは何か

継承とは、すでにあるクラスのフィールドやメソッドを受け継いで、新しいクラスを作る仕組みです。

鬼滅の刃風にたとえると、まず DemonSlayer という鬼殺隊士の共通クラスがあるとします。
このクラスには、鬼殺隊士なら多くの人が持っている基本情報をまとめます。

たとえば、次のようなものです。

項目内容
名前隊士の名前
階級隊士としての階級
行動状態を表示する、任務に出る

この共通の土台をもとにして、さらに特別な隊士を表すクラスを作れます。

たとえば、柱のような特別な役割を持つ隊士を表すなら、次のように考えられます。

クラス名役割
DemonSlayer鬼殺隊士の共通の土台
PillarSlayer鬼殺隊士を受け継いだ特別な隊士

このとき、PillarSlayer は DemonSlayer の機能を受け継ぎます。
さらに、PillarSlayer だけが持つ情報や機能を追加できます。

つまり継承とは、
親クラスの基本能力を受け継ぎながら、子クラスで新しい特徴を加える仕組み
です。

↓クリックすると拡大表示されます。

クラスを拡張するとはどういうことか

クラスを拡張するとは、もともとあるクラスを土台にして、新しいフィールドやメソッドを追加したクラスを作ることです。

鬼滅の刃風にたとえると、普通の鬼殺隊士を土台にして、柱としての役割を追加するイメージです。

たとえば、鬼殺隊士には次のような共通情報があります。

共通情報内容
name名前
rank階級
setSlayer()名前と階級を設定する
show()状態を表示する

ここから、柱クラスには次のような追加情報を持たせます。

追加情報内容
area担当区域
setArea()担当区域を設定する

このとき大切なのは、親クラスにある name、rank、setSlayer()、show() を子クラスにもう一度書かなくてもよいことです。

子クラスは親クラスを受け継いでいるため、親クラスの機能を使えます。
これがクラスを拡張する大きなメリットです。

図:親クラスから子クラスへ受け継ぐイメージ

↓クリックすると拡大表示されます。

この図が示していること

この図では、上にある DemonSlayer クラスが親クラスです。
そこから下の PillarSlayer クラスへ矢印が伸びています。

この矢印は、DemonSlayer のメンバが PillarSlayer に受け継がれていることを表します。

PillarSlayer の中には、親から受け継いだ name、rank、setSlayer()、show() があります。
さらに、PillarSlayer 独自の area、setArea() も追加されています。

つまり、サブクラスは、

受け継ぐもの追加するもの
親クラスの共通機能子クラス独自の特徴

を両方持てるということです。

スーパークラスとサブクラス

継承では、親と子の関係を表すために、次の用語を使います。

用語意味鬼滅の刃風にたとえると
スーパークラス継承元になる親クラス鬼殺隊士という共通の土台
サブクラス親を受け継いで作る子クラス柱のように特別な役割を持つ隊士

今回の例では、次の関係になります。

クラス名役割
DemonSlayerスーパークラス
PillarSlayerサブクラス

ここで大切なのは、サブクラスはスーパークラスとまったく別の存在ではないという点です。

PillarSlayer は、PillarSlayer としての独自要素を持ちながら、DemonSlayer としての性質も持っています。

鬼滅の刃風にたとえると、

柱は特別な隊士だが、まず鬼殺隊士でもある

という関係です。

このように、子クラスは親クラスの一種として考えると、継承の関係が理解しやすくなります。

extends の意味

Javaでクラスを継承するときは、extends を使います。

書き方は次の形です。

class サブクラス名 extends スーパークラス名
{
    サブクラスに追加するメンバ
}

たとえば、PillarSlayer が DemonSlayer を継承する場合は、次のように書きます。

class PillarSlayer extends DemonSlayer
{
    // 柱だけの追加要素
}

この extends は、
このクラスは指定した親クラスを受け継いで作ります
という意味です。

とても短い記述ですが、意味は大きいです。
この1行によって、PillarSlayer は DemonSlayer にあるメソッドを使えるようになります。

クラスの継承を確認する

ファイル名:Sample1.java

class DemonSlayer
{
    private String name;
    private String rank;

    public DemonSlayer()
    {
        name = "隊士未登録";
        rank = "階級未設定";
        System.out.println("鬼殺隊士クラスの隊士を作成しました。");
    }

    public void setSlayer(String n, String r)
    {
        name = n;
        rank = r;
        System.out.println("名前を" + name + "に、階級を" + rank + "にしました。");
    }

    public void show()
    {
        System.out.println("隊士の名前は" + name + "です。");
        System.out.println("階級は" + rank + "です。");
    }
}

class PillarSlayer extends DemonSlayer
{
    private int area;

    public PillarSlayer()
    {
        area = 0;
        System.out.println("柱クラスの隊士を作成しました。");
    }

    public void setArea(int a)
    {
        area = a;
        System.out.println("担当区域を" + area + "にしました。");
    }
}

class Sample1
{
    public static void main(String[] args)
    {
        PillarSlayer slayer1 = new PillarSlayer();

        slayer1.setSlayer("水月", "水柱");
        slayer1.setArea(7);

        slayer1.show();
    }
}

このプログラムの全体像

このプログラムでは、DemonSlayer がスーパークラス、PillarSlayer がサブクラスです。

それぞれの役割を整理すると、次のようになります。

クラス持っているもの
DemonSlayer名前、階級、名前と階級を設定する機能、状態表示機能
PillarSlayer担当区域、担当区域を設定する機能

ここで注目したいのは、PillarSlayer クラスの中に name、rank、setSlayer()、show() を書いていないことです。

それでも、main メソッドでは次のように使えています。

slayer1.setSlayer("水月", "水柱");
slayer1.show();

これは、PillarSlayer が DemonSlayer を継承しているからです。

つまり、PillarSlayer のオブジェクトは、自分自身が持つ setArea() だけでなく、親クラスから受け継いだ setSlayer() や show() も使えます。

スーパークラス DemonSlayer の役割

まず、親クラスである DemonSlayer を見てみましょう。

class DemonSlayer
{
    private String name;
    private String rank;

ここでは、鬼殺隊士に共通する情報として name と rank を持たせています。

鬼滅の刃風にたとえると、どの隊士にも名前と階級があります。
これは柱だけでなく、一般隊士にも共通する情報です。

だから、DemonSlayer にまとめるのが自然です。

DemonSlayer には、次のメソッドもあります。

メソッド役割
DemonSlayer()隊士を初期状態で作る
setSlayer(String n, String r)名前と階級を設定する
show()隊士の状態を表示する

これらは、特別な隊士だけの機能ではありません。
鬼殺隊士として共通して使えそうな機能です。

継承を使うときは、まずこのように考えます。

見るポイント考え方
全員に共通するか共通するなら親クラスへ
特定の種類だけが持つか特定のものなら子クラスへ

DemonSlayer は、鬼殺隊士全体の共通部分をまとめる役割を持っています。

サブクラス PillarSlayer の役割

次に、子クラスである PillarSlayer を見てみましょう。

class PillarSlayer extends DemonSlayer
{
    private int area;

ここでは、extends DemonSlayer と書いています。
これにより、PillarSlayer は DemonSlayer を継承します。

PillarSlayer では、親クラスから受け継いだ機能に加えて、area を追加しています。

この area は、柱が担当する区域のようなものです。

鬼滅の刃風にたとえると、柱はただの隊士ではなく、特定の地域や任務を任される立場です。
そのため、担当区域という追加情報を持たせています。

さらに、setArea() で担当区域を設定します。

public void setArea(int a)
{
    area = a;
    System.out.println("担当区域を" + area + "にしました。");
}

この setArea() は PillarSlayer 独自のメソッドです。
DemonSlayer にはありません。

つまり PillarSlayer は、

受け継いだ機能独自に追加した機能
setSlayer()setArea()
show()area の管理
name と rank の管理

という形で、親の機能と子の機能を組み合わせています。

サブクラスのコンストラクタ

PillarSlayer のコンストラクタは次の部分です。

public PillarSlayer()
{
    area = 0;
    System.out.println("柱クラスの隊士を作成しました。");
}

ここでは、PillarSlayer 独自のフィールドである area を初期化しています。

鬼滅の刃風にたとえると、柱として隊士を登録したときに、担当区域をいったん 0 にしておくようなイメージです。

ここで見ておきたいのは、サブクラスのコンストラクタでは、子クラスで追加した要素を中心に初期化していることです。

親クラスにある name や rank は DemonSlayer 側で管理しています。
子クラスである PillarSlayer は、自分に追加された area を管理します。

このように、親と子で役割を分けると、コードの見通しがよくなります。

main メソッドの流れ

main メソッドでは、次の流れで処理が進みます。

PillarSlayer slayer1 = new PillarSlayer();

ここでは、PillarSlayer 型のオブジェクトを作っています。
slayer1 は、柱クラスの隊士として生成されます。

次に、名前と階級を設定しています。

slayer1.setSlayer("水月", "水柱");

この setSlayer() は PillarSlayer には書かれていません。
しかし、親クラス DemonSlayer にあるため、PillarSlayer のオブジェクトから呼び出せます。

次に、担当区域を設定しています。

slayer1.setArea(7);

この setArea() は PillarSlayer に追加された独自メソッドです。

最後に、状態を表示しています。

slayer1.show();

show() も DemonSlayer から受け継いだメソッドです。

処理の流れを表にすると、次のようになります。

呼び出し定義されている場所意味
new PillarSlayer()PillarSlayer柱クラスの隊士を作る
setSlayer("水月", "水柱")DemonSlayer名前と階級を設定する
setArea(7)PillarSlayer担当区域を設定する
show()DemonSlayer隊士の状態を表示する

この表を見ると、1つのオブジェクトが親クラスと子クラスの両方の機能を使っていることが分かります。

これが継承の大きな特徴です。

実行結果の例

鬼殺隊士クラスの隊士を作成しました。
柱クラスの隊士を作成しました。
名前を水月に、階級を水柱にしました。
担当区域を7にしました。
隊士の名前は水月です。
階級は水柱です。

実行結果では、まず鬼殺隊士クラスのメッセージが表示され、そのあと柱クラスのメッセージが表示されます。

これは、PillarSlayer のオブジェクトを作るとき、親クラスである DemonSlayer の部分も関係しているためです。

そのあと、名前と階級を設定し、担当区域を設定しています。
最後に show() によって、隊士の名前と階級が表示されます。

継承を使うと何が便利なのか

継承の大きな利点は、共通部分を何度も書かなくてよいことです。

たとえば、PillarSlayer クラスを作るときに、名前や階級、状態表示の処理をもう一度全部書くと、コードが重複してしまいます。

しかし、DemonSlayer に共通部分をまとめておけば、PillarSlayer では追加したいものだけを書けばよくなります。

継承のメリットを整理すると、次のようになります。

利点内容
重複が減る同じフィールドやメソッドを何度も書かなくてよい
見通しがよくなる共通部分と追加部分を分けて考えられる
修正しやすい共通処理を親クラス側でまとめて変更できる
拡張しやすい新しいサブクラスを追加しやすい

鬼滅の刃風にたとえると、鬼殺隊士としての基本情報を毎回書き直す必要はありません。
鬼殺隊士の共通台帳を作っておき、柱なら柱用の追加情報だけを書き足すような感覚です。

図:オブジェクトが親と子の機能を使うイメージ

↓クリックすると拡大表示されます。

この図が示していること

この図では、slayer1 という PillarSlayer オブジェクトの中に、親から受け継いだ機能と、自分で追加した機能が一緒に存在している様子を表しています。

親クラス DemonSlayer からは、setSlayer() や show() を受け継いでいます。
子クラス PillarSlayer では、setArea() や area を追加しています。

つまり、サブクラスのオブジェクトは、

種類使えるもの
親から受け継いだものsetSlayer()、show() など
子で追加したものsetArea()、area など

を両方使えるということです。

継承を鬼滅の刃風に感覚で理解する

鬼殺隊士という共通の型があるとします。
その型には、次のような基本があります。

共通の基本内容
名前誰なのか
階級どの立場なのか
状態表示自分の情報を示す

そこから、柱という特別な型を作るとします。

柱は、鬼殺隊士としての基本を持ちながら、さらに担当区域のような情報を持ちます。

つまり、

鬼殺隊士としての共通部分を受け継ぎ、柱としての特徴を追加する

ということです。

これが継承の感覚です。

Javaのコードでは、この関係を extends で表します。

class PillarSlayer extends DemonSlayer

この1行によって、PillarSlayer は DemonSlayer の性質を受け継いだクラスになります。

継承で大切な親子関係の考え方

継承は便利ですが、何でもかんでも使えばよいわけではありません。
大切なのは、親子関係が自然かどうかです。

継承を使うときは、次のように考えると分かりやすいです。

判断ポイント
子クラスは親クラスの一種と言えるかPillarSlayer は DemonSlayer の一種
共通部分を親にまとめられるか名前、階級、状態表示
子クラスだけの追加要素があるか担当区域、担当区域設定

今回の例では、PillarSlayer は DemonSlayer の一種です。
そのため、継承として自然です。

鬼滅の刃風にたとえると、

関係継承として自然か
柱は鬼殺隊士の一種自然
鬼殺隊士は刀の一種不自然
水柱は柱の一種自然

このように、子クラスは親クラスの一種である、という関係で考えると、継承を使うべき場面が見えてきます。

private のフィールドはどう考えるか

Sample1.java では、DemonSlayer の name と rank に private を付けています。

private String name;
private String rank;

private が付いているフィールドは、そのクラスの外から直接触れません。

つまり、PillarSlayer は DemonSlayer を継承していますが、name や rank を直接操作するのではなく、setSlayer() や show() を通して扱います。

これは、鬼滅の刃風にたとえると、隊士名簿の重要情報を勝手に書き換えられないようにしているイメージです。

書き方意味
private String name名前を外部から直接変更させない
setSlayer()決められた方法で名前と階級を設定する
show()決められた方法で状態を表示する

継承しているからといって、親クラスの private フィールドを子クラスが自由に直接触れるわけではありません。
この点は、継承を学ぶときに大切です。

親クラスと子クラスの役割分担

継承では、親クラスと子クラスの役割分担を意識すると理解しやすくなります。

今回の例では、次のように分かれています。

担当内容
DemonSlayer鬼殺隊士としての共通情報を持つ
PillarSlayer柱としての追加情報を持つ
main メソッドオブジェクトを作って、親と子の機能を使う

親クラスには、共通して使うものを置きます。
子クラスには、その子クラスだけが必要とするものを置きます。

この切り分けができると、クラス設計がかなり分かりやすくなります。

継承は設計の再利用である

継承をひとことで表すなら、設計の再利用です。

一度作った親クラスを活かしながら、新しいクラスを作れます。
これにより、共通部分を繰り返し書かずに済みます。

鬼滅の刃風にたとえると、鬼殺隊士としての共通ルールを最初に作っておきます。
そのうえで、水柱、炎柱、音柱のように、必要な特徴を追加していくイメージです。

Javaで継承を学ぶときは、文法だけを見るのではなく、次の視点を持つと理解しやすくなります。

視点考えること
共通部分どのクラスにも必要なものは何か
追加部分子クラスだけに必要なものは何か
親子関係子クラスは親クラスの一種と言えるか
再利用親クラスの機能を使い回せるか

継承は、クラスを強くするための仕組みです。
親クラスに共通の力をまとめ、子クラスで個性を加えることで、コードは読みやすく、拡張しやすくなります。