Java入門|デフォルトコンストラクタと生成制御のしくみ

作り方まで設計できると、クラスはもっと強くなる。コンストラクタの公開範囲を決めて、安全な生成方法を整えよう

コンストラクタを学んでくると、オブジェクトはただ作れればよいわけではなく、どんな状態で作られるべきかまで考えることが大切だと見えてきます。何も指定しないで基本状態の戦士を作る方法もあれば、最初から戦闘力や気の量を決めて登場させる方法もあります。さらに進むと、「この戦士は、必ず必要な情報をそろえた状態で作られるべきだ」という考え方も出てきます。ここで大事になるのが、デフォルトコンストラクタと、コンストラクタの公開範囲を使った生成制御です。

ドラゴンボールで考えるなら、戦士を登場させる方法にルールを持たせるイメージです。誰でも何も決めずに戦士を出せるようにしておくのか、それとも必ず戦闘力や気の量を指定してから登場させるようにするのか。この違いによって、クラスの安全さや使いやすさはかなり変わります。ここでは、コンストラクタを省略したときに何が起こるのか、private なコンストラクタでどう生成方法をしぼれるのかを、ドラゴンボールのたとえでやさしく整理していきます。

コンストラクタを書かなかったときはどうなるのか

これまでの学習では、コンストラクタを自分で定義すると、オブジェクト作成時にその処理が自動で動くことを学びました。
では、クラスの中にコンストラクタをひとつも書かなかったらどうなるのでしょうか。

Java では、その場合に引数のないコンストラクタが自動で用意されます
これがデフォルトコンストラクタです。

イメージとしては、こんな形です。

class Saiyan
{
    private int power;
    private double energy;
}

このようにコンストラクタを何も書いていなくても、Java は内部的に「引数なしのコンストラクタがあるもの」として扱います。
だから、次のような作り方ができるわけです。

Saiyan goku = new Saiyan();

つまり、デフォルトコンストラクタは、コンストラクタを自分で書かなかったときに自動で用意される、引数なしのコンストラクタです。

ドラゴンボールで考えるデフォルトコンストラクタ

ドラゴンボールで考えると、デフォルトコンストラクタは「特に細かい指定をしないで、とりあえず戦士を1人登場させるための基本入口」のようなものです。

たとえば、

  • 名前はまだ決めない
  • 戦闘力はとりあえず 0
  • 気の量もとりあえず 0.0

のような、何も特別な条件をつけない基本状態で登場させる感覚です。

つまりデフォルトコンストラクタがあると、

細かい指定をしなくても、ひとまずオブジェクトは作れる

ということになります。

これは入門段階ではとても便利です。
でも、いつでもこの自由な作り方がよいとは限りません。
そこが、次の生成制御につながっていくポイントです。

いつでも自由に作れることが、便利とは限らない

一見すると、引数なしで簡単に作れるのはよいことのように見えます。
ですが、クラスによっては「何も指定せずに作れてしまうこと」が困る場合があります。

たとえばドラゴンボールの戦士を考えてみてください。
もしその戦士が、必ず戦闘力と気の量を持って登場すべき存在だとしたら、何も決めずに作れるのは少し不自然です。

つまり、

  • 戦闘力が未定のまま
  • 気の量も未定のまま
  • でもオブジェクトだけは作れてしまう

という状態を避けたいことがあります。

このようなときには、作り方を限定するという考え方が必要になります。
そのために使えるのが、コンストラクタにつける public や private です。

コンストラクタにも public と private をつけられる

これまでメンバに対して public や private を使い分けてきました。
実はコンストラクタにも、同じように修飾子をつけられます。

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

class Saiyan
{
    private int power;
    private double energy;

    private 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 + "です。");
    }
}

このコードでは、

  • 引数なしコンストラクタは private
  • 引数2個のコンストラクタは public

になっています。

ここが大事です。
この違いによって、クラスの外から使える作成方法と、使えない作成方法が分かれます。

private なコンストラクタは、外から使えない

private をつけたコンストラクタは、クラスの外から呼び出せません。
つまり、次のような作り方はできなくなります。

Saiyan goku = new Saiyan();

なぜなら、引数なしコンストラクタが private だからです。

これは、戦士を何も指定せずに作る方法を、クラスの外に対して閉じているということです。
ドラゴンボールで言えば、「基本状態のまま適当に戦士を出す入口は、外には公開していない」という状態です。

このようにすると、使う側は好き勝手な作り方ができなくなります。
でも、そのぶんクラスの設計者が意図した作り方だけを使わせられるようになります。

public なコンストラクタだけを入口として公開する

一方で、public なコンストラクタはクラスの外から使えます。
今回の例では、次のコンストラクタです。

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

これは public なので、外から次のように書けます。

Saiyan vegeta = new Saiyan(9000, 20.5);

この作り方なら、

  • 戦闘力
  • 気の量

の両方を指定したうえで、戦士を作ることになります。

つまり、このクラスでは

  • 引数なしの作り方は不可
  • 引数2個の作り方だけ可

というルールになっているわけです。

これが、コンストラクタを使った生成制御です。

なぜ生成方法を限定すると便利なのか

ここで大事なのは、作り方を減らすことが不便なのではなく、不自然な作り方を防げるから便利だということです。

たとえばこの Saiyan クラスでは、戦士を作るときに必ず

  • 戦闘力
  • 気の量

を指定させるようにできます。

すると、少なくとも外からは

  • 何も決まっていないあいまいな戦士
  • 初期状態だけで放置された戦士

を作りにくくなります。

ドラゴンボールで言えば、「登場するときには最低限この情報を持っていなければならない」というルールを守らせているようなものです。

つまりコンストラクタを private にするのは、ただ意地悪に隠すためではなく、
オブジェクトの作られ方を正しく制御するため
なのです。

private なコンストラクタは、クラスの中では使える

ここで安心しておきたいのは、private なコンストラクタでも、クラスの中からは使えるということです。

今回の例では、引数2個の public コンストラクタの中で、

this();

と書いていました。

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

つまり、

  • クラスの外からは使えない
  • でもクラスの中では使える

という状態です。

これがとても便利です。
なぜなら、共通の初期化処理は private な引数なしコンストラクタにまとめておいて、それを public なコンストラクタの内部で再利用できるからです。

ドラゴンボールで言えば、「基本登場処理は内部専用の儀式として残しておき、それを正式な登場方法の中でだけ使う」ようなものです。

this() と private コンストラクタの組み合わせが便利

この設計では、引数なしコンストラクタを完全に消してしまうのではなく、外には見せず、内部専用として残しておくことができます。

たとえば今回なら、引数なしコンストラクタには

  • power = 0
  • energy = 0.0
  • 「戦士を作成しました。」の表示

という共通処理が入っています。

そして引数2個のコンストラクタでは、まず this(); でその共通処理を呼び、そのあとで

  • power = p
  • energy = e

と上書きしています。

この流れによって、

  • 共通の初期化は1か所にまとめる
  • 外からは正しい作り方だけを許す

という、かなり整理された設計ができます。

デフォルトコンストラクタは「自分で何も定義しないとき」にだけ用意される

ここでひとつ整理しておきたいのが、デフォルトコンストラクタが自動で用意される条件です。

デフォルトコンストラクタは、クラスの中にコンストラクタを1つも定義しなかったときにだけ用意されます。

// デフォルトコンストラクタ
Saiyan()
{
・・・
}

つまり、自分でコンストラクタを1つでも書いたら、もう「自動の引数なしコンストラクタ」は出てきません。

これはとても大切です。
たとえば引数2個のコンストラクタだけを書いたのに、引数なしで作れるつもりでいると、思った通りになりません。

だから、クラス設計では

  • どのコンストラクタを自分で用意するか
  • 引数なしの作り方を許すのか
  • どの作成方法を公開するのか

をきちんと考えることが大切になります。

コンストラクタ設計は「戦士の登場ルール」を決めること

ここまでの内容をドラゴンボールでまとめると、コンストラクタ設計は「戦士の登場ルールを決めること」だと言えます。

たとえば、

  • 誰でも自由に基本状態の戦士を作ってよいのか
  • 必ず戦闘力と気の量を決めてから登場させるべきなのか
  • 基本登場処理は内部専用にしたいのか

といったことを、コンストラクタの公開範囲で決められるわけです。

これはただ文法を覚える話ではなく、クラスをどういう部品として使わせたいかを考える話です。
そこが見えてくると、コンストラクタがかなり立体的に見えてきます。

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

この図の上側では、コンストラクタを何も定義しない場合に、引数なしのデフォルトコンストラクタが自動で用意される流れが示されています。
これによって、「なぜ今まで引数なしでオブジェクトを作れたのか」が視覚的にわかります。

下側では、private な引数なしコンストラクタと、public な引数つきコンストラクタが並んでいます。
ここで、new Saiyan() は外からは使えず、new Saiyan(9000, 20.5) だけが正式な入口になっていることがわかります。

さらに public コンストラクタから private コンストラクタへ this() の矢印が伸びているので、内部では共通の初期化処理を再利用していることも見やすくなっています。

いちばん大事な感覚

「デフォルトコンストラクタと生成制御のしくみ」で大切なのは、次の感覚です。

  • コンストラクタを1つも定義しないと、引数なしのデフォルトコンストラクタが自動で用意される
  • コンストラクタにも public や private をつけられる
  • private なコンストラクタはクラスの外から呼び出せない
  • public なコンストラクタだけを入口にすれば、オブジェクトの作り方を限定できる
  • private なコンストラクタは内部で this() を通して再利用できる

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

  • 戦士を何も指定せずに出せる基本入口がある場合もある
  • でもクラスによっては、その入口を閉じて
  • 必ず戦闘力や気の量を決めた正式な方法でだけ登場させたいことがある
  • その登場ルールを決めるのが、コンストラクタの公開範囲の設計

ということです。

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