
Javaのコンストラクタとは?コンストラクタの構文や使い方を解説
Javaのコンストラクタ(constructor)とは、クラスからインスタンスを作る時に実行される処理のことです。
コンストラクタは必ず実行されますので、インスタンスが持つフィールドに値を設定する際などに使います。
上手く出来たコンストラクタはクラスの使い方を明確にするので、分かりやすく使いやすいクラスになります。
ですが、使い方次第では逆に分かりづらいクラスにもなりえますので、気を付けたいところです。
この記事では、そんなJavaのコンストラクタの使い方や活用の仕方を、初心者向けにお伝えします。
※この記事のサンプルは、Java 10の環境で動作確認しています
1.コンストラクタの基本
Javaのコンストラクタ(constructor)とは、クラスからインスタンスを作る時に実行される処理のことです。
constructorという単語は、英語でのconstruct(作る) + er(~な人)なので、建設業者、製造者、組立員のような意味があります。ですので、イメージとしてはインスタンスを作る人、という感じですね。
1-1.コンストラクタは特別なメソッドのようなもの
コンストラクタは、インスタンスが作られる時に実行される特別なメソッドだとも言えます。ただ、普通のメソッドとは違って、以下のような特徴があります。
- クラス名と同じ名前を持つ
- メソッドとしての戻り値を持たない(し、途中でのreturnもできない)
- インスタンスが作られる時には必ず実行される
これらの他にも、メソッドと同じようなこともできます。
- クラスはいくつでもコンストラクタを持てる(=コンストラクタのオーバーロード)
- アクセス修飾子で、コンストラクタを呼び出せる範囲をコントロールできる
- コンストラクタを実行する時に発生した例外をthrowできる
1-2.コンストラクタの構文
コンストラクタの構文は以下のとおりです。[]で囲った部分は、必須ではないものです。
[アクセス修飾子] クラス名([引数]) [throwする例外] { [コンストラクタでやりたい処理] }
見てお分かりいただけたかと思いますが、メソッドの宣言と書き方はほとんど同じです。でも、よく見ると、メソッドでは必須な戻り値がありませんよね。voidと書く必要もなかったりします。
なぜかと言うと、コンストラクタの戻り値は必ずそのクラスのインスタンスなので、はっきり書かなくてもJavaコンパイラには分かるからです。
つまり、コンストラクタとして最低限必要なのは、クラス名と()と{}を書くことだけです。コンストラクタの中には処理を書かなくてもいいので、中身が空っぽでも平気です。
1-3.new演算子でコンストラクタを呼び出す
コンストラクタはnew演算子を使って呼び出します。逆に、new演算子を使わなければ呼び出せない、とも言えます。
newというキーワードを使うのは、コンストラクタを使ってインスタンスを「新しく」作る処理だからです。「演算子」と呼ばれることからわかるとおり、new演算子の戻り値はコンストラクタで作成した、クラスのインスタンスです。
new演算子の構文は以下のとおりです。コンストラクタには引数があるもの、ないものがありますので、引数は必要に応じて使い分けます。
new クラス名([引数])
なお、Javaプログラマはnewを「インスタンスを作る」という意味の動詞としてよく使いますので、慣れておきましょう。例えば「newする」「newできる」「ヌルポが出るのはそこでnewしていないからだよ」という感じです。
1-4.コンストラクタの例
これがJavaで一番簡単なコンストラクタの例です。とてもシンプルです。
class ConstructorSample1 { ConstructorSample1() { } }
さっそくこのコンストラクタを使ってみます。でも、これだとコンストラクタが動いているのが分からないので、printする処理を追加してみます。
class ConstructorSample1 { ConstructorSample1() { System.out.println("ConstructorSample1のコンストラクタ"); } public static void main(String[] args) { System.out.println("コンストラクタ呼出し前"); new ConstructorSample1(); // → コンストラクタが実行され、メッセージが表示される System.out.println("コンストラクタ呼出し後"); } }
newされたタイミングでコンストラクタが実行される、ということが分かると思います。では、引数があるコンストラクタも作って動かしてみましょう。
class ConstructorSample2 { String name; ConstructorSample2(String str) { name = str; } public static void main(String[] args) { ConstructorSample2 sample1 = new ConstructorSample2("sample"); ConstructorSample2 sample2 = new ConstructorSample2("サンプル"); System.out.println(sample1.name); // → "sample"が表示される System.out.println(sample2.name); // → "サンプル"が表示される } }
コンストラクタの中では、引数のStringをインスタンスのフィールドに設定しています。mainではインスタンスを二つ作っていて、コンストラクタを呼び出す時の引数をそれぞれ別にしています。後から行っているprintでは、それぞれのコンストラクタで設定した異なるStringの値が表示されます。
これで、インスタンスの初期状態をコンストラクタで設定できる、ということの意味が分かるでしょうか。これがコンストラクタのもっとも普通な使われ方です。
もちろん、インスタンスのフィールドはnewした後からでも設定・変更できます。でも、newした直後でもうフィールドに値が設定されていれば、すぐにインスタンスを使えるから楽ちんですよね。
2.コンストラクタのあれこれ
この章では、コンストラクタのもう少し進んだ内容についてお伝えします。ちょっとややこしいこともありますが、順を追って理解していってください。
2-1.デフォルトコンストラクタ
前章で作った引数と処理の無いコンストラクタは、プログラマがクラスにコンストラクタを作らないと、実はJavaが裏でこっそりと作ります。
このようなコンストラクタを「デフォルトコンストラクタ(default constructor)」と呼びます。プログラマが省略した場合のデフォルトのコンストラクタなので、そういう呼ばれ方なのですね。
例えば、以下のコンストラクタの無いクラスでもnewできます。これは当たり前のようですが、あらためて考えてみると少し不思議ではありませんか?
class ConstructorSample3 { public static void main(String[] args) { new ConstructorSample3(); } }
コンストラクタを書いていないのになぜnewできるのか…と言うと、デフォルトコンストラクタが目には見えないけれどもあるからです。
2-2.引数を変えてコンストラクタをオーバーロードする
今までの例では一つのクラスに一つのコンストラクタだけを作ってきました。でも、クラスにはプログラマが必要なだけ、いくつでもコンストラクタを作れます。
つまり、以下のようにコンストラクタの引数を変えれば、コンストラクタをオーバーロードできるのです。
class ConstructorSample4 { // ①引数のないコンストラクタ ConstructorSample4() { } // ②Stringの引数があるコンストラクタ ConstructorSample4(String str) { } // ③Stringとintの引数があるコンストラクタ ConstructorSample4(String str, int i) { } public static void main(String[] args) { new ConstructorSample4(); // ①を呼び出している new ConstructorSample4("sample1"); // ②を呼び出している new ConstructorSample4("sample2", 123); // ③を呼び出している } }
コンストラクタのオーバーロードは、メソッドのオーバーロードと同じ考え方です。ですから、引数の型と出てくる順番が違うなら、それぞれ違うコンストラクタになります。
そして、クラスにいくつかコンストラクタがあったなら、newする時にはどれか一つだけを呼び出すことになります。
コンストラクタをオーバーロードするのは、クラスの初期化の仕方を変えたい場合があるからです。普通はデフォルト値でいいけれど、特定の場合だけは初期値を指定したいという場合などです。
2-3.コンストラクタをコンストラクタから呼ぶthis()
コンストラクタをコンストラクタから呼ぶ…一見訳が分かりませんが、プログラムを効率的に作るには、ぜひ使えるようになりたいテクニックです。
2-3-1.コンストラクタでの処理を一箇所にまとめたい
例えば、以下のようにいくつかのコンストラクタを持ったクラスがあるとします。それぞれのコンストラクタに、フィールドnameとamountの初期化処理があります。
class ConstructorSample5 { String name; int amount; // コンストラクタ① ConstructorSample5() { name = "sample"; amount = 100; } // コンストラクタ② ConstructorSample5(String str) { name = str; amount = 100; } // コンストラクタ③ ConstructorSample5(String str, int i) { name = str; amount = i; } }
でも、同じような処理をコピー&ペーストでたくさん作りたくはないですよね。こういう処理はなるべく一箇所にまとめたいです。これを、あるコンストラクタの中から別のコンストラクタを呼ぶことで、効率的に作れたりします。
2-3-2.this()で効率的にプログラムできる例
以下の例では、引数が二つあるコンストラクタ③を、①と②からthis()で呼び出しています。
class ConstructorSample5 { String name; int amount; // コンストラクタ① ConstructorSample5() { this("sample"); // コンストラクタ②を呼び出している } // コンストラクタ② ConstructorSample5(String str) { this(str, 100); // コンストラクタ③を呼び出している } // コンストラクタ③ ConstructorSample5(String str, int i) { this.name = str; this.amount = i; } }
こうすることで、フィールドへ値を設定している所を一つだけにできていますよね。プログラムも短くなって、スッキリしました。
メソッド呼び出しと似ていますが、コンストラクタの中からの呼び出し方は、クラス名ではなくthis()を使います。これを活用すると、一つのコンストラクタでの処理を他からも使えるので、効率的なプログラミングができるのです。
2-3-3.this()の二つのルール
なお、this()には以下の二つのルールがありますので気を付けてください。このルールを守らないとコンパイルエラーとなります。
- コンストラクタの「先頭行」になければならない
- 一つのコンストラクタ内では「一回だけ」呼び出せる
ちなみに、this()は以下の記事でも詳しくお伝えしていますので、参考にしてください。
関連記事2-4.コンストラクタのアクセス修飾子
コンストラクタは、アクセス修飾子で呼び出すことができる範囲を制限できます。これにより、インスタンスを作ってもよいクラスの範囲を、クラスを作ったプログラマが指定できるのです。
コンストラクタへのアクセス修飾子は、メソッドと同じようにpublic/protected/package private(指定なしの場合)/privateの四つのどれかを指定できます。それぞれの意味もメソッドと同じです。
- public:どのパッケージのどのクラスからも呼び出せる
- protected:同じパッケージ、あるは継承先のクラスだけから呼び出せる
- package private:同じパッケージ内のクラスからだけ呼び出せる
- private:自分自身のクラスからしか呼び出せない
そして、あえてコンストラクタを公開しないようにすることで実現できるテクニックもあります。例えば、以下の記事でそれをお伝えしていますので、よろしければ見ていってください。
関連記事ちなみに、デフォルトコンストラクタのアクセス修飾子は、クラスのアクセス修飾子と同じになります。
2-5.コンストラクタはクラスを作ったプログラマの意思表示
コンストラクタは、プログラマがこのクラスのインスタンスはこうして作って欲しい、という意思表示です。
プログラマがクラスのコンストラクタをどう作るかで、クラスのインスタンスをどのように作らせたいかを、クラスを使う人に強制できるのです。例えば以下のように。
- 引数があるコンストラクタが一つしかなければ、それを使ったインスタンス生成しかできません。
- 引数の無いコンストラクタか、デフォルトコンストラクタしかないなら、引数なしのnewしかできません。
- コンストラクタがオーバーロードされていたなら、それらのどれか一つだけを使わなければなりません。
- privateなコンストラクタだけなら、そのクラスは外部からのインスタンス化はできません。
これは、クラスの設計をする時にしっかりと考えないと、使いづらかったり、意図の分かりづらいクラスが出来てしまうということです。
引数が何十個もあるコンストラクタがあったなら、それぞれの引数に正しい値を設定することは、Javadocがあったとしても難しいですよね。オーバーロードされたコンストラクタが何十個もあったなら、それらのコンストラクタをどう使い分ければいいのか困ってしまいますよね。
ですから、コンストラクタを作る時は、初期化で必要十分な引数はどれか、コンストラクタでどこまで初期化できていればいいのかをしっかりと考えるべきです。その考え方の指針については後述します。
3.クラスの継承とコンストラクタ
3-1.スーパークラスのコンストラクタは必ず呼ばれる
クラスが継承の関係にある場合、サブクラスのコンストラクタからは、「必ず」スーパークラスのコンストラクタが呼び出されます。スーパークラスの初期化は、サブクラスの初期化よりも先に行うルールになっています。
スーパークラスにコンストラクタがあれば、そのスーパークラスの作成者が、クラスの初期化の仕方を指定しているということです。ですから、サブクラスはそのやり方を守ってスーパークラスを初期化しなければなりません。
それに、前章でもお伝えしたとおり、明示的にクラスにコンストラクタを作らなくても、クラスはデフォルトコンストラクタを持ちます。スーパークラスといっても普通のクラスですから、必ずコンストラクタがあるということです。
ですから、Javaでは必ずスーパークラスのコンストラクタがあること、そしてそのコンストラクタのいずれかが必ず実行されることが保証されているのです。これは、スーパークラスが抽象クラスであっても同じことです。
3-2.スーパークラスのコンストラクタはsuper()で呼び出す
super()とは、スーパークラスのコンストラクタをサブクラスのコンストラクタの中から呼び出す時に使うものです。this()と同様に、コンストラクタの中でしか呼び出せません。
super()もthis()と似た使い方ですが、呼び出すものがスーパークラスのコンストラクタであるのが違います。そして、super()にもthis()のようなルールがあります。このルールを守らないとコンパイルエラーとなります。
- コンストラクタの「先頭行」になければならない
- 一つのコンストラクタ内で「一回だけ」呼び出せる
- this()をした後にsuper()はできない、super()をした後にthis()はできない
ちなみに、スーパークラスにどのようなコンストラクタがあるかで、サブクラスからの呼び出し方が少々変わります。以下ではそれらの例を順番にお伝えします。
3-2-1.スーパークラスのコンストラクタが引数なしの場合
スーパークラスのコンストラクタが引数を持たないなら、サブクラスでは単にsuper()とするだけです。
class ConstructorSample6Parent { // スーパークラスのコンストラクタ ConstructorSample6Parent() { } } class ConstructorSample6Child extends ConstructorSample6Parent { // サブクラスのコンストラクタ ConstructorSample6Child() { super(); // スーパークラスのコンストラクタを呼び出す } }
3-2-2.スーパークラスのコンストラクタが引数ありの場合
スーパークラスのコンストラクタが引数を持つなら、サブクラスではsuper()に引数を指定して呼び出します。もしスーパークラスのコンストラクタが一つしかなければ、必ずそのコンストラクタを呼ばなければなりません。
class ConstructorSample6Parent { // スーパークラスのコンストラクタ ConstructorSample6Parent(String str) { } } class ConstructorSample6Child extends ConstructorSample6Parent { // サブクラスのコンストラクタ ConstructorSample6Child() { super("sample"); // スーパークラスの引数があるコンストラクタを呼び出す } }
3-2-3.スーパークラスのコンストラクタがオーバーロードされている場合
スーパークラスのコンストラクタがいくつかあるなら、サブクラスではsuper()で呼び出すコンストラクタを一つだけ選びます。呼び出すコンストラクタの区別は、メソッドのオーバーロードやthis()と同じで、引数の型と順番で行います。
class ConstructorSample6Parent { // スーパークラスのコンストラクタ① ConstructorSample6Parent() { } // スーパークラスのコンストラクタ② ConstructorSample6Parent(String str) { } // スーパークラスのコンストラクタ③ ConstructorSample6Parent(String str, int i) { } } class ConstructorSample6Child extends ConstructorSample6Parent { // サブクラスのコンストラクタ ConstructorSample6Child() { super("sample"); // スーパークラスのコンストラクタ②を呼び出している } }
3-2-4.スーパークラスに明示的なコンストラクタがない場合
前述のとおり、明示的なコンストラクタがなくても、クラスにはデフォルトコンストラクタがあります。ですから、以下のケースでもサブクラスからのsuper()はできるのです。
class ConstructorSample6Parent { // スーパークラスのコンストラクタがない = デフォルトコンストラクタがある } class ConstructorSample6Child extends ConstructorSample6Parent { // だからsuper()をサブクラスのコンストラクタで行える ConstructorSample6Child() { super(); } }
引数の無いsuper()では、スーパークラスの引数の無いコンストラクタか、デフォルトコンストラクタを呼び出すことになると覚えておきましょう。
3-3.スーパークラスのコンストラクタは自動的に呼ばれる(ことがある)
以下のケースだと、サブクラスはスーパークラスのコンストラクタを呼び出していないように見えます。でも、コンパイルも実行もできます。
この章の最初で「サブクラスからはスーパークラスのコンストラクタは必ず呼び出される」と書きましたが、嘘だったのでしょうか。いいえ、嘘ではありません。
class ConstructorSample6Parent { // スーパークラスのコンストラクタ ConstructorSample6Parent() { System.out.println("ConstructorSample6Parent"); } } class ConstructorSample6Child extends ConstructorSample6Parent { // サブクラスのコンストラクタ、super()をしていないが… ConstructorSample6Child() { } public static void main(String[] args) { new ConstructorSample6Child(); // → "ConstructorSample6Parent"が表示される } }
サブクラスのコンストラクタでsuper()をしないと、スーパークラスのデフォルトコンストラクタあるいは引数の無いコンストラクタを、自動的に呼び出します。これは「暗黙的なコンストラクタ呼び出し」とも呼ばれます。
ですが、スーパークラスのコンストラクタがデフォルトコンストラクタか引数がないものだけ「ではない」なら、そのコンストラクタを呼ばないとコンパイルエラーです。
class ConstructorSample6Parent { // スーパークラスのコンストラクタには、引数ありのものしかない ConstructorSample6Parent(String str) { } } class ConstructorSample6Child extends ConstructorSample6Parent { // コンパイルエラー!! // サブクラスのコンストラクタから、スーパークラスの引数ありコンストラクタを呼び出していないため ConstructorSample6Child() { } }
これで「サブクラスからはスーパークラスのコンストラクタは必ず呼び出される」の意味が分かったでしょうか。この様に、コンストラクタでの初期化は、クラスを継承した場合でも必ず行われるのです。
4.【応用】コンストラクタのさらに進んだ話題
4-1.使いやすいコンストラクタの指針
コンストラクタはプログラマが自由に作れます。自由に作れるということは、プログラマのスキルや知識次第で、分かりやすくも分かりづらくもなるのです。
ここでは、皆が使いやすい/使いづらいコンストラクタの指針を、いくつかお伝えします。
4-1-1.引数が多いコンストラクタは使いづらい
コンストラクタは、一般的には引数が多ければ多いほど、使い方が難しくなっていきます。
私の個人的な感覚では、引数は多くても5~6個程度、それ以上では引数が多すぎるな、と感じ始めてきます。
例えば、クラスにインスタンスフィールドが多いからと、何十個も引数があるコンストラクタを作っては、いかにも使いづらそうですよね。フィールドと引数のマッチングをきちんとプログラミングするだけでも大変そうです。
それに、Javaはいわゆる名前付き引数や引数のデフォルト値が使えませんから、その意味でも引数が多すぎるコンストラクタは使いづらいものになります。
4-1-2.オーバーロードされすぎたコンストラクタは使いづらい
コンストラクタはいくらでもオーバーロード出来るからと、違いがほんの少しだけのコンストラクタをたくさん作ってしまうと、クラスを使う側は混乱します。
こちらも私の個人的な感覚ですが、やはり5~6個程度を超えると「なんでこんなにあるんだろう?」と感じ始めます。
オーバーロードされているどのコンストラクタをどういう時に使えばいいのか。クラスを作ったプログラマにはきちんとした意図があっても、使う側では簡単には分からないものです。
それに、Javaのオーバーロードの仕様では、型とその順番でしか区別できないので、違いが分かりづらくなります。引数の型と順番が同じなのに、こちらのStringはXの意味、こちらのStringはYの意味、とはできないのです。
4-1-3.コンストラクタとsetterや初期化メソッドを使い分ける
以上のことは、全ての初期化処理をコンストラクタの中だけで行おうとしていると起きがちです。確かにコンストラクタは初期化に使うものですが、コンストラクタは初期化の万能ツールではないのです。
インスタンスの初期化には大体以下のやり方があります。これらは、クラスの特性や初期化で必須な情報が何かを考えて、柔軟に組み合わせるべきものです。
- コンストラクタで値を指定する
- インスタンス生成後に、setterでプロパティを設定する
- インスタンス生成後に、専用の初期化メソッドを呼び出すのをルールにする
- インスタンス生成後に、初期化を担当する専用クラスのメソッドに初期化を任せる
- DIコンテナやIoCコンテナなどの、インスタンス生成を助けてくれるフレームワーク・ミドルウェアに任せる
※DI = Dependency Injection(依存性の注入)、IoC = Inversion of Control(制御の反転)
その上で、コンストラクタの引数にすべきものの指針としては、以下が考えられます。私個人がこういうルールとしているだけで絶対的なものではありませんが、それほど変なものでもないはずです。
- インスタンスのキー情報であり、他のインスタンスとの識別にも使えるもの
- インスタンスにとって必要不可欠なもの
- インスタンスにとって、一旦設定されれば変更不要なもの(finalなフィールドなど)
- 複数の値に相関関係があり、整合性をもって設定しなければならないもの
- 初期化処理にしか使わず、かつインスタンスのフィールドでは保持しないもの
重要なのは、そのクラスやインスタンスにとって主であるものと、従であるものの区別をきちんとつけることです。
例えば、RDBMSのテーブルにおける一行を表現するクラスなら、主キー列(PK)だけはコンストラクタで指定し、その他の列はsetterで指定させるなどです。
4-2.コンストラクタとfinalなインスタンスフィールド
Javaでのfinalは、フィールドに使えばそれが指す値や、指す先のインスタンスを変えられないというものです。このfinalは、コンストラクタによるインスタンスの初期化と、とても相性がいいものです。
なぜかというと、コンストラクタでfinalなフィールドを設定すれば、インスタンスが生きている間は絶対に変わらない、一種の定数のように扱えるからです。
これについては、以下のfinalの記事が参考になるかと思います。興味があればご覧ください。
関連記事4-3.インスタンスフィールドの初期値設定・初期化とコンストラクタの実行タイミング
インスタンスフィールドの初期値の設定と初期化、コンストラクタの実行タイミングを理解しておかないと、思わぬ動きをした時の原因を把握するのに時間がかかってしまいます。
Javaでは、インスタンスが生成された時には、以下の順番で処理が行われます。
- インスタンスフィールドを初期値で設定(数値は0、booleanはfalse、参照型はnull)
- スーパークラスのコンストラクタを実行
- インスタンスフィールドの明示的な初期化と、インスタンスイニシャライザを実行
- コンストラクタを実行する
2.でスーパークラスのコンストラクタが実行されたら、スーパークラス側で同じように1.から実行します。ですので、例えばクラスの継承関係がスーパークラスから順に「A→B→C」なら、全体として以下で実行されます。
クラスC 1→2
┗クラスB 1→2
┗クラスA 1→(2→)3→4
┗クラスB 3→4
┗クラスC 3→4
ちなみに、インスタンス生成の順番は、ここでの説明からも分かるとおり、継承関係で一番下位のクラスからです。このため、スーパークラスの初期化時は、インスタンスメソッドがオーバーライド済みの状態で初期化が進みます。
あとは、1.と3.の違い、すなわち初期値と初期化の違いが分かりづらいかもしれません。フィールドの宣言と初期値を同時にした場合、例えば int i = 123; としても、処理が3.に来るまでは i の値は 0 だということです。
5.まとめ
この記事では、Javaのコンストラクタをお伝えしてきました。
コンストラクタは、クラスから作られるインスタンスへの初期化処理を行いたい場合に使います。コンストラクタはメソッドのようなもので、引数を変えればオーバーロードもできます。
this()やsuper()を使えば、異なるコンストラクタや、スーパークラスのコンストラクタを呼び出せます。
コンストラクタは、きちんと考えないと、使いづらいクラスになってしまいます。その際の指針の一つは、インスタンスに必須な情報が何かや、情報の主従関係がどうなのかを判断することです。
コンストラクタはインスタンスを作成する際には絶対に使う、Javaでは必要不可欠なものです。コンストラクタを自由自在に使いこなして、インスタンスの初期化は任せてください、と言えるようになりましょう!!
コメント