Java超|this()でコンストラクタを再利用する

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

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

ここまでで、引数なしのコンストラクタで基本状態の戦士を作る方法や、引数ありのコンストラクタで最初から戦闘力や気の量を指定して戦士を作る方法を見てきました。

たとえば、ドラゴンボールの世界観でたとえると、戦士を作るときに次のような登場パターンを用意できます。

作り方ドラゴンボールでたとえると
引数なしコンストラクタ基本状態の戦士として登場する
引数ありコンストラクタ戦闘力や気の量が整った状態で登場する

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

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

戦闘力を 0 にする
気の量を 0.0 にする
戦士を作成しました。と表示する

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

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

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

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

ドラゴンボールでたとえると、まず全戦士共通の登場準備を行い、そのあとで特定の戦士だけ追加の初期設定を行うような流れです。

この記事では、具体的なプログラムは Sample6.java を使って、この this() の働きをじっくり整理していきます。

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

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

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

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

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

コンストラクタ役割
Saiyan()基本状態で戦士を作る
Saiyan(int p, double e)戦闘力と気の量を指定して戦士を作る

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

戦闘力の初期値を整える。
気の量の初期値を整える。
戦士を作成したことを知らせる。

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

そこで使うのが this() です。

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

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

ドラゴンボールでたとえると、共通準備のあとに個別設定を追加する

ドラゴンボールでたとえると、this() は、まず全戦士共通の登場準備を行い、そのあとで個別の設定を追加するためのしくみです。

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

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

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

個別設定内容
戦闘力10000
気の量20.5

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

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

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

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

図:this()でコンストラクタを再利用する

この図が示していること

この図では、引数ありコンストラクタの中から this() を使って、共通準備を行う引数なしコンストラクタを呼び出す流れを表しています。

最初に power = 0、kiEnergy = 0.0、戦士を作成しました。という共通の初期化が行われます。
そのあとで、個別設定として power = 10000、kiEnergy = 20.5 が追加されます。

ここから分かるのは、this() を使うと、共通処理を1か所にまとめたまま、必要な追加設定だけを後ろにつけ足せるということです。

this() の動きを確認する

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

ファイル名:Sample6.java

class Saiyan
{
    private int power;
    private double kiEnergy;

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

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

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

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

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

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

public Saiyan()

public Saiyan(int p, double e)

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

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

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

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

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

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

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

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

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

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

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

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

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

そこで、

power = 0
kiEnergy = 0.0
戦士を作成しました。の表示

が行われます。

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

power = p
kiEnergy = e

が実行されます。

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

new Saiyan() の場合の流れ

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

Saiyan goku = new Saiyan();
goku.show();

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

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

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

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

フィールド
power0
kiEnergy0.0

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

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

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

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

ドラゴンボールでたとえると、悟空が基本状態で登場し、その状態を報告している流れです。

new Saiyan(10000, 20.5) の場合の流れ

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

Saiyan vegeta = new Saiyan(10000, 20.5);
vegeta.show();

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

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

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

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

this();

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

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

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

処理内容
power = 0戦闘力を基本値にする
kiEnergy = 0.0気の量を基本値にする
表示戦士を作成しました。

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

power = p;
kiEnergy = e;

p には 10000、e には 20.5 が入っています。

実引数仮引数フィールド
10000ppower
20.5ekiEnergy

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

フィールド最終的な値
power10000
kiEnergy20.5

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

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

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

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

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

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

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

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

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

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

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

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

ドラゴンボールでたとえると、vegeta はまず全戦士共通の登場準備を行い、そのあとで戦闘力10000、気の量20.5の準備済み戦士として整えられた、という流れです。

図:this() で共通処理をまとめる

この図が示していること

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

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

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

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

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

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

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

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

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

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

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

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

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

this();
power = p;
kiEnergy = e;

このように、

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

という流れにできます。

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

ドラゴンボールでたとえると、全戦士共通の登場準備を各戦士ごとに毎回書くのではなく、共通の準備所で一度行ってから、個別の準備を追加するようなものです。

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

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

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

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

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

処理役割
this();共通の登場準備を行う
power = p;戦闘力を指定値にする
kiEnergy = e;気の量を指定値にする
表示個別設定を知らせる

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

ドラゴンボールでたとえると、まず全員共通の戦士登録を済ませ、そのあとで、この戦士は戦闘力10000、気の量20.5で戦闘開始、という追加設定をしているようなイメージです。

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

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

this();

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

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

this(10000, 20.5);

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

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

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

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

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

this() は必ず先頭に書く

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

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

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

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

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

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

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

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

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

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

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

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

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

図:this() は別コンストラクタを呼ぶ特別な処理

この図が示していること

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ドラゴンボールでたとえると、全戦士共通の登場準備を一か所にまとめ、特別な戦士だけ追加設定を行う形です。

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

this と this() は別の使い方

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

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

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

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

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

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

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

this();

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

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

いちばん大事な感覚

this() でコンストラクタを再利用するときに大切なのは、次の感覚です。

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

ドラゴンボールでたとえると、this() は戦士の登場準備をつなぐしくみです。

まず全戦士共通の準備を行う。
そのあとで、特定の戦士だけ戦闘力や気の量を追加設定する。

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

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