Java入門|サブクラスのオブジェクト作成とコンストラクタのしくみ

親の力を受け継いだ戦士は、コンストラクタの流れまでしっかりつながっている。
サブクラスのオブジェクト作成を理解すると、継承の動きがぐっと立体的に見えてくる。

継承の基本が見えてくると、次に気になってくるのが
「では、サブクラスのオブジェクトを実際に作ると何が起こるのか」
という点です。

Javaでは、サブクラスのオブジェクトも new で作成します。見た目はいつものオブジェクト作成とあまり変わりません。けれども内部では、スーパークラスから受け継いだ部分の初期化と、サブクラス独自の初期化が順番に行われています。ここを理解すると、継承がただの文法ではなく、クラス同士がきちんと連携して動く仕組みだとわかってきます。

ドラゴンボールでたとえるなら、これは新しい戦士が登場するときの流れに似ています。
たとえばエリートサイヤ人を登場させるとき、その戦士はいきなりエリートとしてだけ存在するのではありません。まずサイヤ人としての基本的な性質が整えられ、そのうえでエリート戦士としての任務や役割が加わります。

今回はこの流れを、ドラゴンボールの戦士たちに置きかえながら、

  • サブクラスのオブジェクトをどう作るのか
  • 継承したメソッドと追加したメソッドをどう使うのか
  • コンストラクタはどの順番で動くのか
  • super() と this() はどう違うのか

という点を、やわらかく丁寧に見ていきます。

サブクラスのオブジェクトはどう作るのか

サブクラスのオブジェクト作成そのものは、特別に難しいものではありません。
基本はこれまでと同じで、new を使えばよいです。

ドラゴンボールで考えると、Saiyan という共通の戦士クラスをもとに、EliteSaiyan というサブクラスを作っていたとします。すると、エリートサイヤ人のオブジェクトもふつうに new EliteSaiyan() で作成できます。

ここで大切なのは、作成されたオブジェクトが

  • サブクラス独自の機能を持つ
  • スーパークラスから受け継いだ機能も持つ

という点です。

つまりサブクラスのオブジェクトは、ただ新しい機能だけを持つのではなく、親から受け継いだ力もあわせ持っています。
ドラゴンボールらしく言えば、

エリート戦士として生まれるが、同時にサイヤ人としての基本能力も最初から備えている

という感覚です。

継承したメソッドと追加したメソッドはどちらも使える

サブクラスのオブジェクトが便利なのは、親から受け継いだメソッドも、自分で追加したメソッドも、同じように呼び出せるところです。

たとえば今回のドラゴンボール風の設計では、

  • スーパークラス Saiyan にあるメソッド
    setSaiyan()
  • サブクラス EliteSaiyan に追加したメソッド
    setArea()

の両方を、EliteSaiyan のオブジェクトから呼び出せます。

これはかなり大事なポイントです。
なぜなら、継承とは「親のメンバをコピーして書き直すこと」ではなく、親の機能をそのまま受け継いで使えるようにすることだからです。

Sample1.java のコード

サブクラスのオブジェクト作成と、継承したメソッド・追加したメソッドの呼び出しが確認できるドラゴンボール風のサンプルプログラムです。

ファイル名: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;
        warrior1 = new EliteSaiyan();

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

Sample1.java の実行の流れ

このコードでは、まず EliteSaiyan 型の変数 warrior1 を用意し、そのあとで new EliteSaiyan() によってオブジェクトを作成しています。

そのあとに呼び出しているのが次の2つです。

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

この2行は見た目こそ似ていますが、実は役割が少し違います。

呼び出し定義されている場所意味
setSaiyan("ベジータ", 18000)Saiyan継承したメソッドを使って名前と戦闘力を設定する
setArea(7)EliteSaiyanサブクラスで追加したメソッドを使って担当エリアを設定する

この表からわかるように、warrior1 はサブクラスのオブジェクトですが、親クラスにある機能もふつうに使えています。

ドラゴンボールでたとえると、

  • ベジータという戦士名と戦闘力を設定するのは、サイヤ人としての基本情報
  • 担当エリアを設定するのは、エリート戦士としての追加任務

というイメージです。

つまり、サブクラスのオブジェクトは
親の基本能力と、自分の追加能力をあわせて扱える
わけです。

Sample1.java の出力から見えること

このコードを実行すると、考え方としては次のような流れになります。

  1. サイヤ人としての土台が作られる
  2. エリートサイヤ人としての追加部分が作られる
  3. 名前と戦闘力が設定される
  4. 担当エリアが設定される

ここで特に注目したいのが、オブジェクト作成時の出力順です。
サブクラスのオブジェクトを作っているのに、最初にスーパークラス側のコンストラクタの処理が動きます。

これはJavaの継承でとても重要なルールです。

サブクラスのオブジェクト作成で最初に何が起きるのか

new EliteSaiyan() を実行すると、Javaはまずサブクラスだけを見て処理するわけではありません。
最初にスーパークラス側の初期化を行い、そのあとでサブクラス側の初期化を行います。

この順番はとても自然です。
なぜなら、エリートサイヤ人はまずサイヤ人として成り立っていないといけないからです。

ドラゴンボールの感覚で言えば、

  • 先に「サイヤ人としての基本の力」を整える
  • そのあとで「エリート戦士としての任務や個性」を加える

という順番になります。

だからコンストラクタの処理も、

  1. スーパークラスのコンストラクタ
  2. サブクラスのコンストラクタ

の順で進みます。

コンストラクタは継承されない

ここでひとつ大切な点があります。
それは、コンストラクタは継承されない ということです。

メソッドやフィールドは継承の対象になりますが、コンストラクタはそのまま子クラスに受け継がれるわけではありません。
その代わり、サブクラスのコンストラクタの先頭で、スーパークラスのコンストラクタが呼び出される仕組みがあります。

何も書かない場合は、自動的にスーパークラスの引数なしコンストラクタが呼ばれます。

このしくみのおかげで、スーパークラスが持っているフィールドもきちんと初期化されるわけです。

何も指定しないと引数なしのコンストラクタが呼ばれる

Sample1.java では、EliteSaiyan のコンストラクタの先頭に特別な指定を書いていません。
この場合、Javaは自動的にスーパークラスの引数なしコンストラクタを呼び出します。

つまり、内部では次のようなイメージで処理されています。

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

実際には super(); を書かなくても、書いたものとして扱われるイメージです。

これによって、Saiyan 側の

  • name
  • power

がきちんと初期化されます。

この流れを表で整理するとわかりやすいです。

順番実行される処理内容
1Saiyan()名前と戦闘力の初期化、サイヤ人作成メッセージ
2EliteSaiyan()担当エリアの初期化、エリートサイヤ人作成メッセージ

この順序を知っておくと、継承を使ったクラス設計の見通しがかなり良くなります。

図でコンストラクタの流れをつかむ

この図は、new EliteSaiyan() をしたときに、いきなりサブクラスだけが作られるのではなく、先に Saiyan() が動くことを表しています。

これは、親の土台ができてから子の個性が完成する、という継承の考え方そのものです。

スーパークラスの特定のコンストラクタを呼びたいとき

ここまでは、何も指定しなかった場合の流れでした。
ただ、スーパークラスにコンストラクタが複数あるときには、どれを呼び出すか自分で選びたいことがあります。

そんなときに使うのが super() です。

super() をサブクラスのコンストラクタの先頭に書くと、スーパークラスのどのコンストラクタを呼び出すか指定できます。

これはとても大切です。
なぜなら、親クラス側で最初からしっかり値を入れて初期化したい場合に役立つからです。

サンプルプログラム:super() を用いたコンストラクタの呼び出し

次は super() を使って、スーパークラスの引数付きコンストラクタを呼び出す例です。

ファイル名:Sample2.java

class Saiyan
{
    private String name;
    private int power;

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

    public Saiyan(String n, int p)
    {
        name = n;
        power = p;
        System.out.println("名前が" + name + "で戦闘力が" + power + "のサイヤ人を作成しました。");
    }

    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 EliteSaiyan(String n, int p, int a)
    {
        super(n, p);
        area = a;
        System.out.println("担当エリアが" + area + "のエリートサイヤ人を作成しました。");
    }

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

class Sample2
{
    public static void main(String[] args)
    {
        EliteSaiyan warrior1 = new EliteSaiyan("ベジータ", 18000, 7);
    }
}

サンプルプログラム で見える super() の役割

このプログラムで注目したいのは、EliteSaiyan の引数3個のコンストラクタです。

public EliteSaiyan(String n, int p, int a)
{
    super(n, p);
    area = a;
    System.out.println("担当エリアが" + area + "のエリートサイヤ人を作成しました。");
}

ここで最初に super(n, p); と書かれています。
これにより、スーパークラス Saiyan の引数2個のコンストラクタが呼ばれます。

つまり、処理の流れは次のようになります。

順番実行される処理内容
1super(n, p)名前と戦闘力を持つサイヤ人として初期化する
2area = aサブクラス独自の担当エリアを設定する
3メッセージ出力エリートサイヤ人として完成したことを表示する

ここでは「引数なしのコンストラクタ」ではなく、「名前と戦闘力を受け取るコンストラクタ」を明示的に指定しています。
これが super() の大きな役目です。

super() を使うと何がうれしいのか

super() を使うと、スーパークラス側の初期化をより自然な形で行えます。

今回なら、Saiyan に対して

  • 名前
  • 戦闘力

を最初から渡して初期化しています。
これによって、あとから setSaiyan() を呼んで値を入れるよりも、オブジェクト作成の段階で状態が整います。

ドラゴンボールでたとえると、これは

登場した瞬間に、すでにベジータとしての名前と戦闘力を持った状態で現れる

ようなイメージです。

とても自然ですね。

super() はコンストラクタの先頭に書く

super() にはルールがあります。
それは、コンストラクタの先頭に書かなければならない ことです。

たとえば次のような順番にはできません。

public EliteSaiyan(String n, int p, int a)
{
    area = a;
    super(n, p);
}

このような書き方はできません。
必ず最初に super(...) が来ます。

これは、親クラスの初期化が済む前に、子クラスの処理を進めないようにするためです。
Javaはこの順序をとても厳密に守っています。

this() と super() の違い

ここでよく似たものとして this() があります。
どちらもコンストラクタの中で使うものですが、意味は違います。

記述役割
this()同じクラスの別のコンストラクタを呼び出す
super()スーパークラスのコンストラクタを呼び出す

この違いはしっかり区別しておきたいところです。

ドラゴンボール風に言うなら、

  • this() は「同じ戦士クラスの別の登場パターンを呼ぶ」
  • super() は「親の戦士クラスの初期化を呼ぶ」

という違いです。

this() と super() を同時には書けない理由

this() も super() も、どちらもコンストラクタの先頭に書かなければなりません。
ということは、同じコンストラクタの中で両方を同時に書くことはできません。

たとえばこんな形はできません。

public EliteSaiyan(String n, int p, int a)
{
    this();
    super(n, p);
}

どちらも先頭でなければいけないので、両立しないわけです。

このルールは最初は少し機械的に見えるかもしれませんが、考え方としてはすっきりしています。
Javaは「このコンストラクタは、まずどこから処理を始めるのか」を1つに決めさせているのです。

継承とコンストラクタの関係をドラゴンボールで整理する

ここまでの流れを、ドラゴンボールの感覚で整理するとこうなります。

まず、サイヤ人という共通の土台があります。
その土台には、名前や戦闘力のような基本情報があります。

その上に、エリートサイヤ人という特別な戦士を作ります。
この戦士には、担当エリアのような独自情報もあります。

オブジェクトを作るときは、

  1. まずサイヤ人としての基礎を整える
  2. そのあとでエリート戦士としての要素を追加する

という順番になります。

これは継承の考え方とぴったり一致しています。

  • スーパークラスは共通の土台
  • サブクラスはその土台の上に個性を加えた存在
  • コンストラクタはその順序を守って動く

こう考えると、サブクラスのオブジェクト作成はかなりわかりやすくなります。

Javaのクラスの強みとして見ておきたいこと

継承は、単独で便利なだけではありません。
カプセル化や多態性と並んで、Javaのクラスが強い理由のひとつになっています。

継承があると、

  • すでに作ったクラスを土台にできる
  • 共通部分を再利用できる
  • 新しいクラスを効率よく増やせる

ようになります。

今回の Saiyan と EliteSaiyan でも、親に共通部分を置いておいたおかげで、子クラスでは本当に必要な追加部分だけに集中できました。

これは実際のプログラム作成でもとても大きな利点です。

この内容でつかんでおきたいポイント

今回の内容で、とくに押さえておきたい点を表にするとこうなります。

ポイント内容
サブクラスのオブジェクト作成new でこれまでと同じように作成できる
継承したメソッドサブクラスのオブジェクトからそのまま呼び出せる
追加したメソッドサブクラス独自の機能として同じように呼び出せる
コンストラクタの順序先にスーパークラス、次にサブクラス
super()呼び出すスーパークラスのコンストラクタを指定できる
this()同じクラス内の別のコンストラクタを呼び出せる
注意点super() と this() はどちらも先頭に書く

このあたりがしっかり見えてくると、継承のコードを読んだときに「今どこで初期化されているのか」「なぜこの順番なのか」がつかみやすくなります。

クラスのつながりが見えると、継承はぐっとわかりやすい

継承は、ただ extends を書く技術ではありません。
どの情報を親に置き、どの情報を子に置き、オブジェクト作成時にどの順番で初期化するか、という設計そのものに関わっています。

ドラゴンボールの戦士で考えると、

  • サイヤ人としての共通部分がある
  • そこからエリートサイヤ人という発展形が作られる
  • 登場時にはまず親の力が整い、そのあとで子の個性が加わる

という流れになります。

この感覚がつかめると、サブクラスのオブジェクト作成とコンストラクタのしくみは、とても自然に理解できるようになります。