Java入門|継承のしくみとクラスの拡張

親の力を受け継ぎ、戦士はさらに進化する。
継承を知ると、クラス設計はもっと強く、もっとわかりやすくなる。

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

継承を覚えると、似たような性質を持つクラスを毎回ゼロから作らなくてよくなります。共通している部分は親クラスにまとめておき、そこから必要な特徴だけを追加していけばよいからです。こうすると、コードが整理しやすくなり、読みやすさも上がります。

ドラゴンボールでたとえると、この考え方はとても自然です。
たとえば、悟空やベジータ、悟飯はみんな「戦士」という共通の性質を持っています。名前があり、戦うことができ、気を扱える存在です。でも、それぞれには固有の個性もあります。悟空ならかめはめ波、ベジータならファイナルフラッシュ、悟飯なら魔閃光というように、共通点と個別の特徴の両方があります。

このような
共通部分は親にまとめる
個別の特徴は子に追加する
という考え方が、継承の基本です。

今回は「継承のしくみとクラスの拡張」を、ドラゴンボールの戦士たちに置きかえながら、やさしく丁寧に見ていきます。

継承とは何か

継承は、すでにあるクラスの性質や機能を受け継いで、新しいクラスを作るしくみです。

ドラゴンボールで考えると、まずは共通の土台として Saiyan というクラスがあるイメージです。ここには、サイヤ人の戦士に共通する情報や動きをまとめておきます。

たとえば、こんな共通要素があります。

項目内容
名前戦士の名前
戦闘力戦士の強さ
行動戦う、状態を表示する

この土台となるクラスをもとにして、さらに特別な戦士クラスを作ることができます。

たとえば今回の考え方では、

  • Saiyan クラス = 共通の戦士の土台
  • EliteSaiyan クラス = 特別な任務や能力を持つ拡張クラス

のように考えられます。

つまり継承とは、
親クラスのよさを受け継ぎながら、子クラスに新しい力を追加すること
です。

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

クラスを拡張するとは、もともとあるクラスを土台にして、そこへ新しいメンバを付け足したクラスを作ることです。

ドラゴンボールでいえば、ただの戦士ではなく、特別な役割を持つ戦士を作るイメージです。

たとえば共通の戦士クラスが次のようなものだとします。

  • 名前を持つ
  • 戦闘力を持つ
  • 自分の状態を表示できる

ここから、新しいクラスにだけ必要なものを追加します。
たとえばエリート戦士なら、

  • 配属エリアを持つ
  • 配属エリアを設定する機能を持つ

といった具合です。

このとき大切なのは、親クラスにあるメンバを子クラスにもう一度書かなくてよいことです。
親にあるものは、そのまま受け継がれるからです。

このしくみのおかげで、コードを何度も重複して書かずにすみます。

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

継承では、親と子の関係を表すために専用の呼び名があります。

用語意味ドラゴンボールでのたとえ
スーパークラス継承元になる親クラスサイヤ人という共通の戦士の土台
サブクラスその親を受け継いで作る子クラス特別任務を持つ戦士、個性を追加した戦士

今回のたとえで整理すると、次のようになります。

クラス名役割
Saiyanスーパークラス
EliteSaiyanサブクラス

ここで大事なのは、サブクラスはまったく別物ではなく、スーパークラスの性質を引き継いだ存在だということです。

つまり EliteSaiyan は、新しいクラスではありますが、同時に Saiyan としての性質も持っています。

この関係をドラゴンボールらしく言うなら、

エリート戦士は特別な戦士だが、まずはサイヤ人でもある

ということです。

この図で見てほしいのは、上の Saiyan クラスにあるメンバが、下の EliteSaiyan クラスにも受け継がれていることです。

ただし、下のクラスにはさらに area や setArea() のような独自要素も追加されています。
これが「クラスの拡張」です。

つまりサブクラスは、

  • 親の力をそのまま受け取る
  • 足りない特徴だけを自分で加える

という構造になっています。

extends の意味

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

書き方は次の形です。

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

この extends は、
このクラスは、親クラスを受け継いで作ります
という宣言です。

ドラゴンボールのたとえで書けば、こんなイメージです。

class EliteSaiyan extends Saiyan
{
    // エリート戦士だけの追加要素
}

この1行で、EliteSaiyan は Saiyan のメンバを受け継げるようになります。

とても短い記述ですが、意味はかなり大きいです。
この宣言があるだけで、親クラスのフィールドやメソッドを土台として使えるようになるからです。

Sample1.java の全体像

ここからは、継承のしくみをドラゴンボール風のサンプルプログラムで見ていきます。
登場するのは Saiyan クラスと、それを拡張した EliteSaiyan クラスです。

親クラスには共通の情報を入れ、子クラスには追加の情報を入れます。
これによって、継承の考え方がかなりはっきり見えてきます。

ファイル名:Sample1.java

class Saiyan
{
    private String name;
    private int power;

    public Saiyan()
    {
        name = "戦士なし";
        power = 0;
        System.out.println("サイヤ人クラスの戦士を作成しました。");
    }

    public void setSaiyan(String n, int p)
    {
        name = n;
        power = p;
        System.out.println("名前を" + name + "に戦闘力を" + power + "にしました。");
    }

    public void show()
    {
        System.out.println("戦士の名前は" + name + "です。");
        System.out.println("戦闘力は" + power + "です。");
    }
}

class EliteSaiyan extends Saiyan
{
    private int area;

    public EliteSaiyan()
    {
        area = 0;
        System.out.println("エリートサイヤ人を作成しました。");
    }

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

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

        warrior1.setSaiyan("ベジータ", 18000);
        warrior1.setArea(7);

        warrior1.show();
    }
}

このプログラムのポイント

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

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

クラス持っているもの
Saiyan名前、戦闘力、共通の設定機能、状態表示機能
EliteSaiyan担当エリア、担当エリア設定機能

ここで注目したいのは、EliteSaiyan クラスの中に name や power、setSaiyan()、show() を書いていないことです。
それでも warrior1.setSaiyan(...) や warrior1.show() を使えています。

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

つまり EliteSaiyan は、自分で持っている area だけでなく、親である Saiyan の機能も使えるわけです。

スーパークラス Saiyan の役割

まず Saiyan クラスを見ていきましょう。

class Saiyan
{
    private String name;
    private int power;
    ...
}

ここでは、サイヤ人に共通する情報として name と power を持たせています。
ドラゴンボールで考えると、これは戦士なら誰でも持っていそうな基本情報です。

さらに、次のようなメソッドがあります。

メソッド役割
Saiyan()戦士を初期状態で作る
setSaiyan(String n, int p)名前と戦闘力を設定する
show()戦士の状態を表示する

これらはエリート戦士だけの機能ではなく、広くサイヤ人全体に共通するものです。
だからこそ、親クラスにまとめるのが自然です。

設計の考え方としては、

共通して使うものは親に置く

これが継承を使うときの大きな基本です。

サブクラス EliteSaiyan の役割

次に EliteSaiyan クラスです。

class EliteSaiyan extends Saiyan
{
    private int area;
    ...
}

このクラスでは、親クラスの機能は受け継いだうえで、エリート戦士だけが持つ area を追加しています。

ここでの area は、たとえば担当する戦闘区域や任務エリアのようなイメージで考えるとわかりやすいです。

さらに、setArea() というメソッドでその値を設定しています。

つまり EliteSaiyan は、

  • サイヤ人としての共通機能を持つ
  • さらにエリート戦士としての独自機能を持つ

という二段構えのクラスになっています。

これはまさに継承らしい形です。

サブクラスのコンストラクタを見る

EliteSaiyan のコンストラクタは次のようになっています。

public EliteSaiyan()
{
    area = 0;
    System.out.println("エリートサイヤ人を作成しました。");
}

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

この部分からもわかるのは、サブクラスでは
自分に追加した要素についてコードを書く
という考え方です。

親クラスが持っている共通部分は親側で用意しておき、子クラスでは子クラスにしかない特徴を中心に書いていくと、役割分担がとてもきれいになります。

mainメソッドで何が起きているか

main メソッドの流れを順番に見てみましょう。

EliteSaiyan warrior1 = new EliteSaiyan();

ここでは EliteSaiyan 型のオブジェクトを1つ作っています。
この時点で、warrior1 はエリート戦士として生まれています。

次に、

warrior1.setSaiyan("ベジータ", 18000);

を実行しています。
この setSaiyan() は EliteSaiyan クラスには書かれていません。
でも親クラス Saiyan にあるので使えます。

さらに、

warrior1.setArea(7);

では、サブクラス独自の機能を使っています。

最後に、

warrior1.show();

で状態を表示しています。
これも親クラスから受け継いだメソッドです。

流れを表にすると、こうなります。

呼び出し定義されている場所意味
new EliteSaiyan()EliteSaiyanエリート戦士を作る
setSaiyan("ベジータ", 18000)Saiyan名前と戦闘力を設定する
setArea(7)EliteSaiyan担当エリアを設定する
show()Saiyan戦士の状態を表示する

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

継承を使うと何がうれしいのか

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

たとえば、もし継承を使わずにエリート戦士クラスを作るとしたら、

  • 名前
  • 戦闘力
  • 状態表示
  • 設定メソッド

などをもう一度書かなくてはいけなくなるかもしれません。

でも継承を使えば、共通部分は Saiyan にまとめておけばよく、EliteSaiyan には独自の area だけを追加すれば済みます。

その結果、次のようなメリットがあります。

利点内容
重複が減る同じコードを何度も書かなくてすむ
見通しがよくなる共通部分と追加部分の役割分担がはっきりする
修正しやすい共通処理を親クラスでまとめて直せる
拡張しやすい新しいサブクラスを追加しやすい

オブジェクト指向では、こうした「共通化」と「拡張性」がとても大切です。
継承は、その中心にある考え方のひとつです。

ドラゴンボールで継承を感覚的に理解する

ここで、もう一度ドラゴンボールの感覚で整理してみましょう。

たとえば「サイヤ人」という共通の戦士の型があるとします。
その型には、

  • 名前がある
  • 戦闘力がある
  • 自分の情報を表示できる

という共通ルールがあります。

そこから「エリートサイヤ人」という新しい型を作るとします。
この新しい型は、サイヤ人としての基本をそのまま持ちながら、さらに

  • 担当エリアを持つ
  • 任務に応じた設定ができる

という追加要素を持たせられます。

つまり、

親の力を引き継ぎ、子で個性を足す

これが継承です。

この感覚がつかめると、継承は急にわかりやすくなります。

継承で気をつけたい見方

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

今回なら、

  • EliteSaiyan は Saiyan の一種

なので継承が自然です。

つまり
子クラスは親クラスの一種である
と言える関係で使うと理解しやすくなります。

ドラゴンボールの世界でも、

  • 悟空は戦士の一種
  • ベジータも戦士の一種
  • エリートサイヤ人もサイヤ人の一種

という関係なら、継承として整理しやすいです。

この見方は、今後もっと多くのクラスを設計するときにも役立ちます。

この内容でつかんでおきたいキーワード

最後に、この内容で出てきた言葉を整理しておきます。

キーワード意味
継承親クラスのメンバを受け継いで新しいクラスを作ること
拡張受け継いだうえで、新しいメンバを追加すること
スーパークラス継承元の親クラス
サブクラススーパークラスを受け継いだ子クラス
extendsクラスを継承するときに使うキーワード

これらは11章以降のオブジェクト指向でとても重要な言葉です。
特に extends、スーパークラス、サブクラスの3つは、コードを読むときにも書くときにも何度も出てきます。

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

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

一度作った親クラスをそのまま活かしながら、新しいクラスへ発展させることができます。
これは、ドラゴンボールの戦士たちが共通の土台を持ちながら、それぞれ違う個性を育てていく感じによく似ています。

Javaで継承を学ぶときは、ただ文法として覚えるだけでなく、

  • 何を親クラスに置くのか
  • 何を子クラスに追加するのか
  • どこまでが共通で、どこからが個性なのか

を意識して見ることが大切です。

そうすると、クラス設計の見え方がぐっとはっきりしてきます。