Java入門|コンストラクタ同士をつなぐthis()のしくみ

共通の初期化はまとめて呼ぶ。this() がわかると、コンストラクタのつながりがすっきり見えてくる

「コンストラクタ同士をつなぐthis()のしくみ」の序章
コンストラクタを学ぶと、オブジェクトを作るときに最初の状態を自動で整えられることが見えてきます。引数なしで基本状態の戦士を作ることもできましたし、引数つきで最初から戦闘力や気の量を決めた状態の戦士を作ることもできました。ここまでくると、今度は「似たような初期化処理がコンストラクタの中で重なっている」と気づく場面が出てきます。そういうときに活躍するのが、コンストラクタの中から別のコンストラクタを呼び出す this() です。

ドラゴンボールで考えるなら、戦士を登場させるときに、まず全員共通の基本準備をしてから、必要に応じて個別の設定を追加したい場面がありますよね。たとえば、どんな戦士でも最初に「戦士を作成しました」と表示して、戦闘力と気の量の基本値を整えておき、そのあとで特定の戦士だけ戦闘力や気の量を上書きする、という流れです。この共通部分を毎回別々に書くのではなく、すでにあるコンストラクタを呼び出して再利用できるようにするのが this() の役目です。ここでは、そのしくみをドラゴンボールのたとえでやさしく整理していきます。

コンストラクタ同士もつなげられる

これまでの学習では、コンストラクタはオブジェクト作成時に自動で動く特別な処理でした。
そして、引数の違いによって複数のコンストラクタを用意できることも学びました。

ただ、コンストラクタを複数作ると、共通の初期化処理が重なりやすくなります。
たとえば、

  • 基本状態として power を 0 にする
  • energy を 0.0 にする
  • 「戦士を作成しました。」と表示する

という処理が、引数なしコンストラクタにも、引数つきコンストラクタにも必要になることがあります。

このとき、同じ内容を何度も書くのは少し不便です。
そこで使えるのが this() です。

this() を使うと、あるコンストラクタの中から、同じクラスの別のコンストラクタを呼び出すことができます。

ドラゴンボールで考える this() の感覚

ドラゴンボールで考えると、this() は「まず共通の登場準備をしてから、追加の設定をする」ためのしくみだと思うとわかりやすいです。

たとえば、戦士が登場するときには全員共通で、

  • 基本状態を整える
  • 登場メッセージを出す

という準備をしたいとします。

そのうえで、ある戦士だけはさらに

  • 戦闘力を 9000 にする
  • 気の量を 20.5 にする

といった個別設定を追加したいわけです。

このとき、

  1. まず共通のコンストラクタを呼ぶ
  2. そのあと個別の値を設定する

という流れにできると、とても自然です。

this() は、まさにその「まず共通の準備を呼び出す」ための道具です。

実際のプログラムで見てみる

では、サンプルプログラムをドラゴンボールの上のたとえに合わせて整理してみましょう。

ファイル名:Sample6.java

class Saiyan
{
    private int power;
    private double energy;

    public Saiyan()
    {
        power = 0;
        energy = 0.0;
        System.out.println("戦士を作成しました。");
    }

    public Saiyan(int p, double e)
    {
        this();
        power = p;
        energy = e;
        System.out.println("戦闘力を" + power + "に気の量を" + energy + "にしました。");
    }

    public void show()
    {
        System.out.println("戦闘力は" + power + "です。");
        System.out.println("気の量は" + energy + "です。");
    }
}

class Sample6
{
    public static void main(String[] args)
    {
        Saiyan goku = new Saiyan();
        goku.show();

        Saiyan vegeta = new Saiyan(9000, 20.5);
        vegeta.show();
    }
}

このプログラムには、2つのコンストラクタがあります。

  • 引数なしの Saiyan()
  • 引数2個の Saiyan(int p, double e)

注目したいのは、引数2個のコンストラクタの先頭にある this(); です。

this() が呼ばれると、別のコンストラクタが動く

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

public Saiyan(int p, double e)
{
    this();
    power = p;
    energy = e;
    System.out.println("戦闘力を" + power + "に気の量を" + energy + "にしました。");
}

ここで最初に this(); が書かれています。
これは、同じクラスの引数なしコンストラクタを呼び出すという意味です。

つまり、new Saiyan(9000, 20.5) を実行すると、流れはこうなります。

  1. 引数2個のコンストラクタに入る
  2. 先頭の this(); が実行される
  3. 引数なしコンストラクタが呼び出される
  4. そこで power = 0、energy = 0.0、登場メッセージ表示が行われる
  5. 引数2個のコンストラクタに戻る
  6. power = p、energy = e が実行される
  7. 個別設定のメッセージが表示される

この流れによって、「共通の初期化」を使い回しながら、「個別の初期化」を追加できるわけです。

実行結果を見ると流れがよくわかる

このプログラムを実行すると、次のような流れになります。

戦士を作成しました。
戦闘力は0です。
気の量は0.0です。

戦士を作成しました。
戦闘力を9000に気の量を20.5にしました。
戦闘力は9000です。
気の量は20.5です。

最初の goku は引数なしコンストラクタで作られているので、

  • 戦士を作成しました。
  • 戦闘力は0です。
  • 気の量は0.0です。

となります。

次の vegeta は引数2個のコンストラクタで作られていますが、その中で this(); によって引数なしコンストラクタも呼ばれているので、まず

  • 戦士を作成しました。

が表示されます。

そのあとで引数2個のコンストラクタ自身の処理として、

  • 戦闘力を9000に気の量を20.5にしました。

が表示されます。

つまり、この出力からも
this() によって別のコンストラクタが先に動いている
ことがはっきりわかります。

this() を使うと、共通処理をまとめられる

this() のいちばん大きな利点は、共通する初期化処理をひとつにまとめられることです。

もし this() を使わずに書くとしたら、引数2個のコンストラクタの中にも

  • power = 0;
  • energy = 0.0;
  • System.out.println("戦士を作成しました。");

のような共通処理を毎回書かなければならないかもしれません。

でも this() を使えば、その共通部分は引数なしコンストラクタにまとめておけます。
すると引数2個のコンストラクタでは、

  • まず共通部分を呼び出す
  • 次に個別設定を追加する

という流れだけ書けばよくなります。

これはコードをすっきりさせるだけでなく、共通部分をあとで直したいときにも便利です。
1か所直せば済むからです。

「つけ加えるように定義する」という感覚が大事

this() の感覚をひとことで言うなら、

別のコンストラクタの処理に、あとから処理をつけ加える

という感じです。

今回なら、引数2個のコンストラクタは、引数なしコンストラクタの処理に対して、

  • 戦闘力を指定値に変える
  • 気の量を指定値に変える
  • 個別設定のメッセージを出す

という追加をしているわけです。

ドラゴンボールで言えば、まず全戦士共通の登場準備を行ってから、そのあとで「この戦士は特別に戦闘力 9000、気の量 20.5 で登場する」と細かい設定を上乗せしているイメージです。

この「共通処理に追加する」という見方ができると、this() の意味がぐっとわかりやすくなります。

引数つきの this() も使える

今回は this(); で引数なしコンストラクタを呼び出しました。
でも、this() は引数をつけて使うこともできます。

たとえば、同じクラスの中に引数2個のコンストラクタがあるなら、

this(9000, 20.5);

のように書くことで、その引数2個のコンストラクタを呼び出せます。

つまり this() は、

  • this();
    → 引数なしコンストラクタを呼ぶ
  • this(9000, 20.5);
    → 引数2個のコンストラクタを呼ぶ

というふうに使えます。

ここでもやはり大切なのは、渡した引数に合う別のコンストラクタが選ばれるということです。

これはコンストラクタのオーバーロードと自然につながっています。

this() は必ず先頭に書かなければならない

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

つまり、次のような書き方はだめです。

public Saiyan(int p, double e)
{
    power = p;
    this();
    energy = e;
}

このように途中に書くことはできません。

正しくは、最初に書きます。

public Saiyan(int p, double e)
{
    this();
    power = p;
    energy = e;
}

なぜかというと、別のコンストラクタを呼ぶなら、その初期化は最初に済ませなければならないからです。
あとから途中で別のコンストラクタに飛ぶような流れにすると、初期化の順番がわかりにくくなってしまいます。

だから Java では、this() は必ず先頭というルールになっています。

コンストラクタのオーバーロードと this() は相性がよい

ここまで学んだ

  • コンストラクタのオーバーロード
  • this() による別コンストラクタ呼び出し

は、とても相性がよいです。

なぜなら、コンストラクタを複数用意すると、どうしても共通部分と個別部分が出てくるからです。

たとえば、

  • 引数なしコンストラクタ
    → 基本状態を作る
  • 引数2個コンストラクタ
    → 基本状態を作ったあと、指定値に変える

という関係なら、共通部分を引数なしコンストラクタにまとめ、個別部分だけを引数2個コンストラクタに書けばよいわけです。

つまり this() は、オーバーロードしたコンストラクタ同士をきれいにつなげるための道具だと言えます。

図で見ると流れがわかりやすい

この図は、左側に2種類のオブジェクト作成方法があり、それぞれ対応するコンストラクタへ進んでいます。
特に大事なのは、new Saiyan(9000, 20.5) から Saiyan(int p, double e) に入り、そこからさらに this() によって Saiyan() へ進む流れです。

これによって、引数2個のコンストラクタが単独で動くのではなく、まず引数なしコンストラクタの処理を呼び出し、そのあとで自分の追加処理をしていることが視覚的にわかります。

右側では最終的に vegeta オブジェクトが power = 9000、energy = 20.5 の状態で完成しています。
つまり、共通の初期化と個別の初期化が順番につながって、最終的なオブジェクト状態ができあがることが見やすくなっています。

いちばん大事な感覚

「コンストラクタ同士をつなぐthis()のしくみ」で大切なのは、次の感覚です。

  • this() を使うと、あるコンストラクタの中から別のコンストラクタを呼び出せる
  • 共通の初期化処理を再利用しながら、個別の初期化を追加できる
  • 引数つきの this() を使えば、別の引数つきコンストラクタも呼び出せる
  • this() は必ずコンストラクタの先頭に書かなければならない

ドラゴンボールで言いかえるなら、

  • まず全戦士共通の登場準備を行い
  • そのあとで特定の戦士だけ追加の初期設定をする
  • その流れを、すでにあるコンストラクタを呼び出してきれいにつなげる

ということです。

この感覚がつかめると、this() はただの特別な文法ではなく、コンストラクタの重複を減らしながら、初期化の流れを整理するための便利な道具として見えてきます。これが理解できると、クラス設計の見通しがさらによくなります。