Javaについて徹底解説!

基礎から始めるJavaのコンストラクタ 構文から上手な使い方まで

大石 英人

開発エンジニア/Java20年/Java GOLD/リーダー/ボールド歴2年

Javaのコンストラクタ(constructor)とは、クラスからインスタンスを作る時に実行される処理のことです。コンストラクタは必ず実行されますので、インスタンスが持つフィールドに値を設定する際などに使います。

上手く出来たコンストラクタはクラスの使い方を明確にするので、分かりやすく使いやすいクラスになります。ですが、使い方次第では逆に分かりづらいクラスにもなりえますので、気を付けたいところです。

この記事では、そんなJavaのコンストラクタの使い方や活用の仕方を、初心者向けにお伝えします。

※この記事のサンプルは、Java 10の環境で動作確認しています


1.コンストラクタの基本

Javaのコンストラクタ(constructor)とは、クラスからインスタンスを作る時に実行される処理のことです。

constructorという単語は、英語でのconstruct(作る) + er(~な人)なので、建設業者、製造者、組立員のような意味があります。ですので、イメージとしてはインスタンスを作る人、という感じですね。

1-1.コンストラクタは特別なメソッドのようなもの

コンストラクタは、インスタンスが作られる時に実行される特別なメソッドだとも言えます。ただ、普通のメソッドとは違って、以下のような特徴があります。

  • クラス名と同じ名前を持つ
  • メソッドとしての戻り値を持たない(し、途中でのreturnもできない)
  • インスタンスが作られる時には必ず実行される

これらの他にも、メソッドと同じようなこともできます。

  • クラスはいくつでもコンストラクタを持てる(=コンストラクタのオーバーロード)
  • アクセス修飾子で、コンストラクタを呼び出せる範囲をコントロールできる
  • コンストラクタを実行する時に発生した例外をthrowできる

1-2.コンストラクタの構文

コンストラクタの構文は以下のとおりです。[]で囲った部分は、必須ではないものです。

見てお分かりいただけたかと思いますが、メソッドの宣言と書き方はほとんど同じです。でも、よく見ると、メソッドでは必須な戻り値がありませんよね。voidと書く必要もなかったりします。

なぜかと言うと、コンストラクタの戻り値は必ずそのクラスのインスタンスなので、はっきり書かなくてもJavaコンパイラには分かるからです。

つまり、コンストラクタとして最低限必要なのは、クラス名と(){}を書くことだけです。コンストラクタの中には処理を書かなくてもいいので、中身が空っぽでも平気です。

1-3.new演算子でコンストラクタを呼び出す

コンストラクタはnew演算子を使って呼び出します。逆に、new演算子を使わなければ呼び出せない、とも言えます。

newというキーワードを使うのは、コンストラクタを使ってインスタンスを「新しく」作る処理だからです。「演算子」と呼ばれることからわかるとおり、new演算子の戻り値はコンストラクタで作成した、クラスのインスタンスです。

new演算子の構文は以下のとおりです。コンストラクタには引数があるもの、ないものがありますので、引数は必要に応じて使い分けます。

なお、Javaプログラマはnewを「インスタンスを作る」という意味の動詞としてよく使いますので、慣れておきましょう。例えば「newする」「newできる」「ヌルポが出るのはそこでnewしていないからだよ」という感じです。

1-4.コンストラクタの例

これがJavaで一番簡単なコンストラクタの例です。とてもシンプルです。

さっそくこのコンストラクタを使ってみます。でも、これだとコンストラクタが動いているのが分からないので、printする処理を追加してみます。

newされたタイミングでコンストラクタが実行される、ということが分かると思います。では、引数があるコンストラクタも作って動かしてみましょう。

コンストラクタの中では、引数のStringをインスタンスのフィールドに設定しています。mainではインスタンスを二つ作っていて、コンストラクタを呼び出す時の引数をそれぞれ別にしています。後から行っているprintでは、それぞれのコンストラクタで設定した異なるStringの値が表示されます。

これで、インスタンスの初期状態をコンストラクタで設定できる、ということの意味が分かるでしょうか。これがコンストラクタのもっとも普通な使われ方です。

もちろん、インスタンスのフィールドはnewした後からでも設定・変更できます。でも、newした直後でもうフィールドに値が設定されていれば、すぐにインスタンスを使えるから楽ちんですよね。


2.コンストラクタのあれこれ

この章では、コンストラクタのもう少し進んだ内容についてお伝えします。ちょっとややこしいこともありますが、順を追って理解していってください。

2-1.デフォルトコンストラクタ

前章で作った引数と処理の無いコンストラクタは、プログラマがクラスにコンストラクタを作らないと、実はJavaが裏でこっそりと作ります。

このようなコンストラクタを「デフォルトコンストラクタ(default constructor)」と呼びます。プログラマが省略した場合のデフォルトのコンストラクタなので、そういう呼ばれ方なのですね。

例えば、以下のコンストラクタの無いクラスでもnewできます。これは当たり前のようですが、あらためて考えてみると少し不思議ではありませんか?

コンストラクタを書いていないのになぜnewできるのかと言うと、デフォルトコンストラクタが目には見えないけれどもあるからです。

2-2.引数を変えてコンストラクタをオーバーロードする

今までの例では一つのクラスに一つのコンストラクタだけを作ってきました。でも、クラスにはプログラマが必要なだけ、いくつでもコンストラクタを作れます。

つまり、以下のようにコンストラクタの引数を変えれば、コンストラクタをオーバーロードできるのです。

コンストラクタのオーバーロードは、メソッドのオーバーロードと同じ考え方です。ですから、引数の型と出てくる順番が違うなら、それぞれ違うコンストラクタになります。

そして、クラスにいくつかコンストラクタがあったなら、newする時にはどれか一つだけを呼び出すことになります。

コンストラクタをオーバーロードするのは、クラスの初期化の仕方を変えたい場合があるからです。普通はデフォルト値でいいけれど、特定の場合だけは初期値を指定したいという場合などです。

2-3.コンストラクタをコンストラクタから呼ぶthis()

コンストラクタをコンストラクタから呼ぶ一見訳が分かりませんが、プログラムを効率的に作るには、ぜひ使えるようになりたいテクニックです。

2-3-1.コンストラクタでの処理を一箇所にまとめたい

例えば、以下のようにいくつかのコンストラクタを持ったクラスがあるとします。それぞれのコンストラクタに、フィールドnameamountの初期化処理があります。

でも、同じような処理をコピー&ペーストでたくさん作りたくはないですよね。こういう処理はなるべく一箇所にまとめたいです。これを、あるコンストラクタの中から別のコンストラクタを呼ぶことで、効率的に作れたりします。

2-3-2.this()で効率的にプログラムできる例

以下の例では、引数が二つあるコンストラクタを、からthis()で呼び出しています。

こうすることで、フィールドへ値を設定している所を一つだけにできていますよね。プログラムも短くなって、スッキリしました。

メソッド呼び出しと似ていますが、コンストラクタの中からの呼び出し方は、クラス名ではなく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()とするだけです。

3-2-2.スーパークラスのコンストラクタが引数ありの場合

スーパークラスのコンストラクタが引数を持つなら、サブクラスではsuper()に引数を指定して呼び出します。もしスーパークラスのコンストラクタが一つしかなければ、必ずそのコンストラクタを呼ばなければなりません。

3-2-3.スーパークラスのコンストラクタがオーバーロードされている場合

スーパークラスのコンストラクタがいくつかあるなら、サブクラスではsuper()で呼び出すコンストラクタを一つだけ選びます。呼び出すコンストラクタの区別は、メソッドのオーバーロードやthis()と同じで、引数の型と順番で行います。

3-2-4.スーパークラスに明示的なコンストラクタがない場合

前述のとおり、明示的なコンストラクタがなくても、クラスにはデフォルトコンストラクタがあります。ですから、以下のケースでもサブクラスからのsuper()はできるのです。

引数の無いsuper()では、スーパークラスの引数の無いコンストラクタか、デフォルトコンストラクタを呼び出すことになると覚えておきましょう。

3-3.スーパークラスのコンストラクタは自動的に呼ばれる(ことがある)

以下のケースだと、サブクラスはスーパークラスのコンストラクタを呼び出していないように見えます。でも、コンパイルも実行もできます。

この章の最初で「サブクラスからはスーパークラスのコンストラクタは必ず呼び出される」と書きましたが、嘘だったのでしょうか。いいえ、嘘ではありません。

サブクラスのコンストラクタでsuper()をしないと、スーパークラスのデフォルトコンストラクタあるいは引数の無いコンストラクタを、自動的に呼び出します。これは「暗黙的なコンストラクタ呼び出し」とも呼ばれます。

ですが、スーパークラスのコンストラクタがデフォルトコンストラクタか引数がないものだけ「ではない」なら、そのコンストラクタを呼ばないとコンパイルエラーです。

これで「サブクラスからはスーパークラスのコンストラクタは必ず呼び出される」の意味が分かったでしょうか。この様に、コンストラクタでの初期化は、クラスを継承した場合でも必ず行われるのです。


4.【応用】コンストラクタのさらに進んだ話題

4-1.使いやすいコンストラクタの指針

コンストラクタはプログラマが自由に作れます。自由に作れるということは、プログラマのスキルや知識次第で、分かりやすくも分かりづらくもなるのです。

ここでは、皆が使いやすい/使いづらいコンストラクタの指針を、いくつかお伝えします。

4-1-1.引数が多いコンストラクタは使いづらい

コンストラクタは、一般的には引数が多ければ多いほど、使い方が難しくなっていきます。

私の個人的な感覚では、引数は多くても56個程度、それ以上では引数が多すぎるな、と感じ始めてきます。

例えば、クラスにインスタンスフィールドが多いからと、何十個も引数があるコンストラクタを作っては、いかにも使いづらそうですよね。フィールドと引数のマッチングをきちんとプログラミングするだけでも大変そうです。

それに、Javaはいわゆる名前付き引数や引数のデフォルト値が使えませんから、その意味でも引数が多すぎるコンストラクタは使いづらいものになります。

4-1-2.オーバーロードされすぎたコンストラクタは使いづらい

コンストラクタはいくらでもオーバーロード出来るからと、違いがほんの少しだけのコンストラクタをたくさん作ってしまうと、クラスを使う側は混乱します。

こちらも私の個人的な感覚ですが、やはり56個程度を超えると「なんでこんなにあるんだろう?」と感じ始めます。

オーバーロードされているどのコンストラクタをどういう時に使えばいいのか。クラスを作ったプログラマにはきちんとした意図があっても、使う側では簡単には分からないものです。

それに、Javaのオーバーロードの仕様では、型とその順番でしか区別できないので、違いが分かりづらくなります。引数の型と順番が同じなのに、こちらのStringXの意味、こちらのStringYの意味、とはできないのです。

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では、インスタンスが生成された時には、以下の順番で処理が行われます。

  1. インスタンスフィールドを初期値で設定(数値は0booleanfalse、参照型はnull)
  2. スーパークラスのコンストラクタを実行
  3. インスタンスフィールドの明示的な初期化と、インスタンスイニシャライザを実行
  4. コンストラクタを実行する

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では必要不可欠なものです。コンストラクタを自由自在に使いこなして、インスタンスの初期化は任せてください、と言えるようになりましょう!!

私たちは、全てのエンジニアに市場価値を高め自身の望む理想のキャリアを歩んでいただきたいと考えています。もし、今あなたが転職を検討しているのであればこちらの記事をご一読ください。理想のキャリアを実現するためのヒントが見つかるはずです。

8/28(水)開催決定!
【テックジム×ENGINEER.CLUB】ゼロからはじめるPythonプログラミング入門講座

プログラミングは初めてだけどPythonから始めてみたいという方のために、無料のハンズオン開発講座をテックジム×ENGINEER.CLUBで共同開催することになりました。開催日時は以下の通りです。

  • 8月28日(水)19時〜21時 東京開催

本講座で学んでいただく「TechGYM方式」とは、基礎知識なしでも座学なしでプログラミングに専念できるように設計されたプログラミングのカリキュラムメソッドです。「まるで魔法にかかったようにプログラミンスキルが習得できる」と評判の本講座をぜひ一度体験してみてください。

『技術力』と『人間力』を高め市場価値の高いエンジニアを目指しませんか?

私たちは「技術力」だけでなく「人間力」の向上をもって遙かに高い水準の成果を出し、関わる全ての人々に感動を与え続ける集団でありたいと考えています。

高い水準で仕事を進めていただくためにも、弊社では次のような環境を用意しています。

  • 定年までIT業界で働くためのスキル(技術力、人間力)が身につく支援
  • 「給与が上がらない」を解消する6ヶ月に1度の明確な人事評価制度
  • 平均残業時間17時間!毎週の稼動確認を徹底しているから実現できる働きやすい環境

現在、株式会社ボールドでは「キャリア採用」のエントリーを受付中です。

まずは以下のボタンより弊社の紹介をご覧いただき、あなたの望むキャリアビジョンをエントリーフォームより詳しくお聞かせください。