Java道|コンストラクタをつなげるthis()の使い方

共通の初期化は、何度も書かずにつなげて使う。
this() を使えば、コンストラクタ同士の流れがすっきり見える。

コンストラクタを使うと、オブジェクトを作るときに初期状態を自動で整えられます。

前回までに、引数なしのコンストラクタで基本状態の隊士を作る方法や、引数ありのコンストラクタで最初から体力や呼吸の力を指定して隊士を作る方法を学びました。

たとえば、鬼滅の刃でたとえると、隊士を作るときに次のような登場パターンを用意できます。

作り方鬼滅の刃でたとえると
引数なしコンストラクタ基本状態の隊士として登場する
引数ありコンストラクタ体力や呼吸の力が整った状態で登場する

ここまで来ると、次に気になってくるのが「共通する初期化処理を何度も書くのは少しもったいない」という点です。

たとえば、どの隊士を作る場合でも、

体力を 0 にする
呼吸の力を 0.0 にする
隊士を作成しました。と表示する

という共通の準備をしたいことがあります。

そのうえで、引数ありのコンストラクタでは、さらに体力や呼吸の力を指定値に変更したいわけです。

このように、コンストラクタ同士で共通する処理を再利用したいときに使えるのが this() です。

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

鬼滅の刃でたとえると、まず全隊士共通の登場準備を行い、そのあとで特定の隊士だけ追加の初期設定を行うような流れです。

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

コンストラクタは、オブジェクトが作成されたときに自動で動く特別な処理です。

また、引数の違いによって複数のコンストラクタを定義できました。
これをコンストラクタのオーバーロードと呼びます。

ただ、コンストラクタを複数作ると、似た処理が重なりやすくなります。

たとえば、DemonSlayer クラスで次のような2つのコンストラクタを考えます。

コンストラクタ役割
DemonSlayer()基本状態で隊士を作る
DemonSlayer(int s, double e)体力と呼吸の力を指定して隊士を作る

このとき、どちらのコンストラクタでも、最初に共通の準備をしたいことがあります。

体力の初期値を整える。
呼吸の力の初期値を整える。
隊士を作成したことを知らせる。

このような共通処理を、それぞれのコンストラクタに別々に書くと、同じ内容が重複してしまいます。

そこで、this() を使います。

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

つまり、共通の初期化処理を1か所にまとめて、別のコンストラクタから再利用できるようになります。

鬼滅の刃でたとえると、共通準備のあとに個別設定を追加する

鬼滅の刃でたとえると、this() は「まず全隊士共通の登場準備を行い、そのあとで個別の設定を追加する」ためのしくみです。

たとえば、隊士が登場するときには、全員共通で次のような準備を行うとします。

共通準備内容
基本状態を整える体力 0、呼吸の力 0.0 にする
登場メッセージ隊士を作成しました。と表示する

そのうえで、ある隊士だけは最初から次の状態にしたいとします。

個別設定内容
体力100
呼吸の力20.5

このとき、引数ありコンストラクタの中で、まず this() を使って共通準備を呼び出します。
そのあとで、体力と呼吸の力を指定値に上書きします。

流れとしては、次のようになります。

順番処理
1this() で共通の引数なしコンストラクタを呼ぶ
2基本状態に初期化する
3引数ありコンストラクタに戻る
4受け取った値で体力と呼吸の力を設定する

このようにすると、共通準備を何度も書かずに、個別設定だけを追加できます。

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

this() の動きを確認する

実際のプログラムで、this() の動きを見ていきましょう。

ファイル名:Sample6.java

class DemonSlayer
{
    private int stamina;
    private double breathingEnergy;

    public DemonSlayer()
    {
        stamina = 0;
        breathingEnergy = 0.0;
        System.out.println("隊士を作成しました。");
    }

    public DemonSlayer(int s, double e)
    {
        this();
        stamina = s;
        breathingEnergy = e;
        System.out.println("体力を" + stamina + "に呼吸の力を" + breathingEnergy + "にしました。");
    }

    public void show()
    {
        System.out.println("体力は" + stamina + "です。");
        System.out.println("呼吸の力は" + breathingEnergy + "です。");
    }
}

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

        DemonSlayer enma = new DemonSlayer(100, 20.5);
        enma.show();
    }
}

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

public DemonSlayer()

public DemonSlayer(int s, double e)

どちらも DemonSlayer クラスのコンストラクタです。

1つ目は引数なしのコンストラクタです。
2つ目は int と double の2つの引数を受け取るコンストラクタです。

今回の大事なポイントは、引数ありコンストラクタの中にある this(); です。

public DemonSlayer(int s, double e)
{
    this();
    stamina = s;
    breathingEnergy = e;
    System.out.println("体力を" + stamina + "に呼吸の力を" + breathingEnergy + "にしました。");
}

この this(); によって、同じクラスの引数なしコンストラクタを呼び出しています。

this() は別のコンストラクタを呼び出す

this() は、同じクラスの別のコンストラクタを呼び出すための書き方です。

今回のコードでは、引数ありコンストラクタの中で this(); を使っています。

public DemonSlayer(int s, double e)
{
    this();
    stamina = s;
    breathingEnergy = e;
    System.out.println("体力を" + stamina + "に呼吸の力を" + breathingEnergy + "にしました。");
}

ここでの this(); は、次の引数なしコンストラクタを呼び出します。

public DemonSlayer()
{
    stamina = 0;
    breathingEnergy = 0.0;
    System.out.println("隊士を作成しました。");
}

つまり、new DemonSlayer(100, 20.5) が実行されると、最初からいきなり stamina = 100 になるわけではありません。

まず this(); によって、引数なしコンストラクタが動きます。

そこで、

stamina = 0
breathingEnergy = 0.0
隊士を作成しました。の表示

が行われます。

そのあとで、引数ありコンストラクタに戻り、

stamina = s
breathingEnergy = e

が実行されます。

その結果、最終的には、受け取った 100 と 20.5 が入ります。

new DemonSlayer() の場合の流れ

まず、main メソッドの前半を見ます。

DemonSlayer mizuki = new DemonSlayer();
mizuki.show();

new DemonSlayer() では、丸かっこの中に引数がありません。

そのため、Javaは引数なしコンストラクタを選びます。

public DemonSlayer()
{
    stamina = 0;
    breathingEnergy = 0.0;
    System.out.println("隊士を作成しました。");
}

このコンストラクタでは、stamina に 0、breathingEnergy に 0.0 を入れます。

フィールド
stamina0
breathingEnergy0.0

そして、隊士を作成しました。と表示します。

そのあとで show() を呼ぶと、現在の状態が表示されます。

実行結果の前半は次のようになります。

隊士を作成しました。
体力は0です。
呼吸の力は0.0です。

鬼滅の刃でたとえると、水月という隊士が基本状態で登場し、その状態を報告している流れです。

new DemonSlayer(100, 20.5) の場合の流れ

次に、main メソッドの後半を見ます。

DemonSlayer enma = new DemonSlayer(100, 20.5);
enma.show();

new DemonSlayer(100, 20.5) では、int と double の2つの値を渡しています。

そのため、Javaは次の引数ありコンストラクタを選びます。

public DemonSlayer(int s, double e)
{
    this();
    stamina = s;
    breathingEnergy = e;
    System.out.println("体力を" + stamina + "に呼吸の力を" + breathingEnergy + "にしました。");
}

このコンストラクタに入ると、最初に this(); が実行されます。

this();

this(); は引数なしコンストラクタを呼び出します。

public DemonSlayer()
{
    stamina = 0;
    breathingEnergy = 0.0;
    System.out.println("隊士を作成しました。");
}

そのため、まず共通の初期化が行われます。

処理内容
stamina = 0体力を基本値にする
breathingEnergy = 0.0呼吸の力を基本値にする
表示隊士を作成しました。

その後、引数ありコンストラクタに戻り、次の処理が実行されます。

stamina = s;
breathingEnergy = e;

s には 100、e には 20.5 が入っています。

実引数仮引数フィールド
100sstamina
20.5ebreathingEnergy

そのため、最終的に enma は次の状態になります。

フィールド最終的な値
stamina100
breathingEnergy20.5

実行結果の後半は、次のようになります。

隊士を作成しました。
体力を100に呼吸の力を20.5にしました。
体力は100です。
呼吸の力は20.5です。

ここで、隊士を作成しました。が表示されているのは、this(); によって引数なしコンストラクタが呼び出されたからです。

そのあとで、体力を100に呼吸の力を20.5にしました。が表示されています。
これは、引数ありコンストラクタ自身の処理です。

実行結果で流れを確認する

Sample6.java の実行結果をまとめると、次のようになります。

隊士を作成しました。
体力は0です。
呼吸の力は0.0です。

隊士を作成しました。
体力を100に呼吸の力を20.5にしました。
体力は100です。
呼吸の力は20.5です。

この実行結果を見ると、2つ目のオブジェクト enma を作るときにも、隊士を作成しました。が表示されています。

これは、new DemonSlayer(100, 20.5) で引数ありコンストラクタが選ばれ、その中の this(); によって引数なしコンストラクタが先に動いたからです。

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

オブジェクト生成最初に選ばれるコンストラクタthis() で呼ばれるコンストラクタ最終状態
new DemonSlayer()DemonSlayer()なしstamina = 0、breathingEnergy = 0.0
new DemonSlayer(100, 20.5)DemonSlayer(int s, double e)DemonSlayer()stamina = 100、breathingEnergy = 20.5

鬼滅の刃でたとえると、enma はまず全隊士共通の登場準備を行い、そのあとで体力100、呼吸の力20.5の準備済み隊士として整えられた、という流れです。

図:this() で引数なしコンストラクタを呼ぶ流れ

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

この図が示していること

この図では、new DemonSlayer(100, 20.5) によって引数ありコンストラクタが選ばれ、その中の this(); によって引数なしコンストラクタが呼び出される流れを示しています。

まず共通の初期化として stamina = 0、breathingEnergy = 0.0 が実行されます。
その後、引数ありコンストラクタに戻り、stamina = s、breathingEnergy = e によって指定された値へ変更されます。

ここから分かるのは、this() を使うと、共通の初期化を別のコンストラクタにまとめ、それを再利用しながら個別の初期化を追加できるということです。

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

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

もし this() を使わない場合、引数ありコンストラクタの中にも、共通の初期化処理をもう一度書くことになるかもしれません。

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

stamina = 0;
breathingEnergy = 0.0;
System.out.println("隊士を作成しました。");

この処理を複数のコンストラクタに何度も書くと、コードが重複します。

コードが重複すると、あとから変更するときに大変です。

たとえば、作成メッセージを変えたい場合、複数の場所を直す必要が出てきます。
もし一部だけ直し忘れると、コンストラクタごとに動きがずれてしまいます。

this() を使えば、共通処理は引数なしコンストラクタにまとめられます。

そして、引数ありコンストラクタでは次のように書けます。

this();
stamina = s;
breathingEnergy = e;

このように、

まず共通処理を呼ぶ。
そのあと個別設定を追加する。

という流れにできます。

this() を使わない場合this() を使う場合
共通処理を複数のコンストラクタに書きがち共通処理を1か所にまとめられる
修正箇所が増えやすい修正箇所を減らしやすい
初期化の流れがばらつきやすい初期化の流れをそろえやすい

鬼滅の刃でたとえると、全隊士共通の登場準備を各隊士ごとに毎回書くのではなく、共通の準備所で一度行ってから、個別の準備を追加するようなものです。

つけ加えるように定義する感覚

this() を使うときは、「別のコンストラクタの処理に、あとから処理をつけ加える」と考えると分かりやすいです。

今回の引数ありコンストラクタは、引数なしコンストラクタの処理を土台にしています。

public DemonSlayer(int s, double e)
{
    this();
    stamina = s;
    breathingEnergy = e;
    System.out.println("体力を" + stamina + "に呼吸の力を" + breathingEnergy + "にしました。");
}

この流れは、次のように整理できます。

処理役割
this();共通の登場準備を行う
stamina = s;体力を指定値にする
breathingEnergy = e;呼吸の力を指定値にする
表示個別設定を知らせる

つまり、引数ありコンストラクタは、引数なしコンストラクタに対して、指定された体力と呼吸の力を上乗せしているわけです。

鬼滅の刃でたとえると、まず全員共通の隊士登録を済ませ、そのあとで「この隊士は体力100、呼吸の力20.5で任務開始」と追加設定しているようなイメージです。

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

今回のプログラムでは、this(); を使って引数なしコンストラクタを呼び出しました。

this();

ただし、this() は引数をつけて使うこともできます。

たとえば、同じクラスの中に、int と double を受け取るコンストラクタがあるなら、次のように書けます。

this(100, 20.5);

このように書くと、引数の形に合う別のコンストラクタを呼び出します。

書き方呼び出すコンストラクタ
this();引数なしコンストラクタ
this(100, 20.5);int と double を受け取るコンストラクタ

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

同じクラスに複数のコンストラクタがある場合、this() に渡した引数の型や個数によって、どのコンストラクタを呼ぶかが決まります。

ただし、どの向きに呼び出すかは、初期化の流れが分かりやすくなるように考えることが大切です。

this() は必ず先頭に書く

this() には、とても重要なルールがあります。

this() は、コンストラクタの先頭に書かなければなりません。

正しい書き方は、次のような形です。

public DemonSlayer(int s, double e)
{
    this();
    stamina = s;
    breathingEnergy = e;
}

this(); が最初にあります。

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

public DemonSlayer(int s, double e)
{
    stamina = s;
    this();
    breathingEnergy = e;
}

このような書き方はできません。

なぜなら、別のコンストラクタを呼び出すなら、その初期化を最初に済ませる必要があるからです。

途中まで処理をしてから別のコンストラクタを呼ぶと、初期化の順番が分かりにくくなります。
そのため Java では、this() は必ず先頭に書くというルールになっています。

書き方できるか理由
this(); を最初に書くできる先に別コンストラクタの初期化を行う
処理の途中に this(); を書くできない初期化の順番が崩れる

このルールはとても大切です。

this() を使うときは、コンストラクタの最初に書く、と覚えておきましょう。

図:this() は必ず先頭に書く

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

この図が示していること

この図では、this() を書く位置のルールを比較しています。

左側では、this(); がコンストラクタの先頭に書かれています。
これは正しい書き方です。

右側では、stamina = s; のあとに this(); が書かれています。
このように、処理の途中で this() を呼び出すことはできません。

ここから分かるのは、this() は別のコンストラクタを呼ぶための特別な処理なので、必ず最初に書く必要があるということです。

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

コンストラクタのオーバーロードと this() は、とても相性がよいです。

コンストラクタを複数用意すると、共通部分と個別部分が出てきます。

たとえば、今回の Sample6.java では、次のように分かれています。

コンストラクタ役割
DemonSlayer()共通の基本初期化を行う
DemonSlayer(int s, double e)共通初期化を呼び、指定値で上書きする

この関係を作れるのが this() です。

this() を使うことで、引数ありコンストラクタの中で、まず引数なしコンストラクタを呼べます。

public DemonSlayer(int s, double e)
{
    this();
    stamina = s;
    breathingEnergy = e;
    System.out.println("体力を" + stamina + "に呼吸の力を" + breathingEnergy + "にしました。");
}

このようにすると、共通の初期化は DemonSlayer() にまとめられます。
そして、DemonSlayer(int s, double e) には、追加の初期化だけを書けます。

this() を使う目的内容
共通処理の再利用引数なしコンストラクタの処理を使う
重複を減らす同じ初期化を何度も書かない
流れを整理する共通準備 → 個別設定の順にできる
修正しやすくする共通部分を1か所にまとめられる

鬼滅の刃でたとえると、全隊士共通の登場準備を一か所にまとめ、特別な隊士だけ追加設定を行う形です。

この形にすると、隊士の登場処理が整理され、クラス全体の見通しもよくなります。

this と this() は別の使い方

ここで少し注意したいのが、this と this() の違いです。

見た目は似ていますが、役割は違います。

書き方役割
this今のオブジェクト自身を表す
this()同じクラスの別コンストラクタを呼ぶ

this は、自分自身のフィールドやメソッドを表すときに使います。

たとえば、フィールドと引数の名前が同じときに、this.stamina のように使います。

一方で、this() はコンストラクタを呼び出すために使います。

今回の Sample6.java では、this(); によって引数なしコンストラクタを呼び出しています。

this();

つまり、this は自分自身を指す。
this() はコンストラクタを呼ぶ。

この違いを分けて考えると、混乱しにくくなります。

いちばん大事な感覚

コンストラクタをつなげるthis()の使い方で大切なのは、次の感覚です。

ポイント内容
this() の役割同じクラスの別コンストラクタを呼び出す
使う場所コンストラクタの中
書く位置必ずコンストラクタの先頭
便利な点共通の初期化処理を再利用できる
流れ共通準備をしてから個別設定を追加する
this との違いthis は自分自身、this() はコンストラクタ呼び出し

鬼滅の刃でたとえると、this() は隊士の登場準備をつなぐしくみです。

まず全隊士共通の準備を行う。
そのあとで、特定の隊士だけ体力や呼吸の力を追加設定する。

この流れを、すでにあるコンストラクタを呼び出して実現できます。

this() が分かると、コンストラクタの重複を減らし、初期化の流れをきれいに整理できるようになります。