Java入門|引数で変わるコンストラクタのしくみ

登場のしかたに合わせて初期状態も変えられる。コンストラクタのオーバーロードで、戦士づくりはもっと柔軟になる

前の内容では、コンストラクタはオブジェクトが作成された瞬間に自動で動き、最初の状態を整えるための特別な仕組みだと学びました。戦士を作ったときに、戦闘力や気の量を初期値にしておいたり、登場メッセージを出したりできるのがコンストラクタでしたね。

ただ、実際にクラスを使っていくと、いつも同じ初期状態で戦士を作りたいとは限りません。何も指定せずに基本状態の戦士を作りたいこともあれば、最初から戦闘力と気の量を決めた状態で戦士を作りたいこともあります。ドラゴンボールで考えるなら、「とりあえず修行前の戦士として登場させる」場合もあれば、「すでに戦闘準備が整った悟空として登場させる」場合もあるわけです。こうした違いに対応できるのが、コンストラクタのオーバーロードです。ここでは、引数によって使い分けられるコンストラクタのしくみを、ドラゴンボールのたとえでやさしく整理していきます。

コンストラクタもオーバーロードできる

メソッドでは、引数の型や個数が異なっていれば、同じ名前のメソッドを複数定義できました。これがオーバーロードでした。

コンストラクタも、考え方は同じです。
引数の数や型が違っていれば、同じクラスの中に複数のコンストラクタを定義できます。

たとえば Saiyan クラスなら、

  • 引数なしで作るコンストラクタ
  • int と double を受け取って作るコンストラクタ

のように、複数用意できます。

ここで大事なのは、コンストラクタは名前がクラス名と同じなので、見た目は全部同じ Saiyan になることです。
それでも Java は、引数の違いを見て、どのコンストラクタを使うかを判断してくれます。

ドラゴンボールで考えるコンストラクタのオーバーロード

このしくみは、ドラゴンボールで考えるととても自然です。

たとえば戦士を作る方法が2通りあるとします。

  • 何も指定せずに基本状態で作る
  • 戦闘力と気の量を指定して、その状態で作る

同じ「戦士を作る」という目的でも、登場のしかたは場面によって違いますよね。

つまり、

  • とりあえず初期状態の戦士を1人出したい
  • 最初から強さや気を決めた戦士を出したい

という2つの作り方があるわけです。

コンストラクタのオーバーロードは、この「作り方の違い」を自然に表現するためのしくみです。
同じ Saiyan という名前のコンストラクタでも、引数の有無で別の初期化ができるようになります。

引数なしのコンストラクタと引数ありのコンストラクタ

では、実際にどんな形になるのかを見てみましょう。

たとえば Saiyan クラスに、次の2つのコンストラクタを定義できます。

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

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

この2つは、どちらも Saiyan という同じ名前です。
でも、

  • 1つ目は引数なし
  • 2つ目は引数2個

という違いがあります。

そのため Java は、new Saiyan() と書かれたら引数なしのコンストラクタを使い、new Saiyan(9000, 20.5) と書かれたら引数2個のコンストラクタを使います。

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

ここでは、本文の流れをドラゴンボールの世界に置きかえて、プログラムを整理します。

ファイル名:Sample5.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)
    {
        power = p;
        energy = e;
        System.out.println("戦闘力" + power + " 気の量" + energy + "の戦士を作成しました。");
    }

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

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

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

このプログラムでは、Saiyan クラスに2つのコンストラクタがあります。
そして main メソッドの中で、2つのオブジェクトを違う作り方で生成しています。

1つ目のオブジェクトでは引数なしのコンストラクタが使われる

最初のオブジェクト作成はこうです。

Saiyan goku = new Saiyan();

ここでは () の中に何もありません。
だから Java は、引数なしのコンストラクタ

public Saiyan()

を呼び出します。

このコンストラクタでは、

  • power = 0
  • energy = 0.0

と設定しています。

つまり goku は、基本状態の戦士として作られるわけです。
そのあと goku.show(); を呼び出すと、その初期状態が表示されます。

2つ目のオブジェクトでは引数2個のコンストラクタが使われる

次のオブジェクト作成はこうです。

Saiyan vegeta = new Saiyan(9000, 20.5);

ここでは引数が2つあります。

  • 9000 は int
  • 20.5 は double

なので Java は、

public Saiyan(int p, double e)

を選びます。

このコンストラクタでは、

  • power = p
  • energy = e

となるので、vegeta は最初から

  • 戦闘力 9000
  • 気の量 20.5

という状態で作られます。

つまり、作成と同時にかなり具体的な初期状態を与えられているわけです。

実行結果を見ると違いがはっきりする

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

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

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

最初の goku は、何も指定しないで作ったので基本状態です。
一方、vegeta は引数を渡して作ったので、最初から指定された状態で登場しています。

この結果を見ると、コンストラクタをオーバーロードすると、オブジェクトの作り方そのものを使い分けられることがとてもよくわかります。

同じクラスでも、作り方を柔軟に変えられる

コンストラクタのオーバーロードの大きな便利さはここです。

同じ Saiyan クラスを使っていても、

  • 何も指定せずに作る
  • 必要な値を指定して作る

という複数の作り方を用意できます。

これはクラスを使う側から見ると、とてもありがたいです。
なぜなら、場面に応じて自然な作り方を選べるからです。

たとえば、

  • とりあえず基本状態で作りたいときは new Saiyan()
  • 最初から強さを決めて作りたいときは new Saiyan(9000, 20.5)

というように使い分けられます。

同じクラスを使うのに、毎回あとから set メソッドで値を入れなくてもよい場面が増えるので、コードも自然になります。

コンストラクタのオーバーロードは「作成時の選択肢」を増やす

メソッドのオーバーロードは「呼び出す処理の選択肢」を増やしました。
それに対して、コンストラクタのオーバーロードは「オブジェクト作成時の選択肢」を増やします。

この違いを表にすると、こう整理できます。

しくみ役割
メソッドのオーバーロード同じ名前の機能を引数で使い分ける
コンストラクタのオーバーロード同じクラスの作り方を引数で使い分ける

つまりコンストラクタのオーバーロードは、戦士をどういう状態で登場させるかを、作成時点で柔軟に決められるようにするものです。

ドラゴンボールで言えば、修行前の素の状態で登場させるか、最初からある程度仕上がった状態で登場させるかを選べるようなものです。

引数によって適切なコンストラクタが自動で選ばれる

ここでの大事なポイントは、Java が引数を見て自動的に適切なコンストラクタを選んでくれることです。

今回なら、

  • new Saiyan()
    → 引数なしコンストラクタ
  • new Saiyan(9000, 20.5)
    → 引数2個コンストラクタ

という対応になります。

使う側は、どのコンストラクタが呼ばれるかをいちいち細かく指定する必要はありません。
渡した引数の形に応じて、自動で合うものが選ばれます。

この点は、メソッドのオーバーロードとまったく同じ感覚です。
だから、9.2節のオーバーロードを理解していると、コンストラクタのオーバーロードもとても受け入れやすくなります。

初期設定をいろいろ変えられるのが便利

コンストラクタを1つだけにしてしまうと、オブジェクトはいつも同じ作られ方になります。
でも実際には、いろいろな初期設定がほしい場面があります。

たとえば戦士なら、

  • 基本状態で作る
  • 戦闘力だけ指定して作る
  • 戦闘力と気の量を両方指定して作る

といった作り方があると便利です。

今回はサンプルとして引数なしと引数2個の2種類だけですが、考え方としてはもっと広げられます。
つまり、コンストラクタのオーバーロードは、クラスの「誕生パターン」を増やす仕組みなのです。

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

この図では、左側に2種類のオブジェクト作成方法が並んでいます。
上は引数なし、下は引数2個の作成です。

中央の Saiyan クラスには、2種類のコンストラクタがあり、それぞれに対応する矢印が引かれています。
これによって、渡した引数の違いによって、どのコンストラクタが呼ばれるかが視覚的にわかります。

右側では、それぞれの結果としてできあがったオブジェクトの中身が表示されています。
goku は初期状態、vegeta は指定した値を持った状態で作られていて、コンストラクタの違いがオブジェクトの初期状態の違いとして表れていることが見えやすくなります。

いちばん大事な感覚

「引数で変わるコンストラクタのしくみ」で大切なのは、次の感覚です。

  • コンストラクタもオーバーロードできる
  • 引数の数や型が違えば、複数のコンストラクタを定義できる
  • オブジェクト作成時に渡した引数に応じて、適切なコンストラクタが自動で選ばれる
  • その結果、オブジェクトをいろいろな初期状態で柔軟に作れる

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

  • 戦士を登場させる方法を1つに固定せず
  • 基本状態で作る方法と
  • 最初から戦闘力や気の量を決めて作る方法を
  • 同じ Saiyan クラスの中に用意できる

ということです。

この感覚がつかめると、コンストラクタは単なる初期化処理ではなく、オブジェクトの生まれ方そのものを設計する仕組みとして見えてきます。ここまで理解できると、クラス設計の見え方がまた一段深くなります。