コンストラクタ

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

コンストラクタ とは

オブジェクトを生まれた瞬間に初期化する仕組み、コンストラクタを学ぼう。new でクラスを呼び出すときに何が起きているのかを追いかけながら、Book クラスを組み立てて本のフルタイトルを返すメソッドを完成させる。

オブジェクトが生まれる瞬間を司る関数

Java でクラスからオブジェクトを作るときに、必ずひと仕事するのが コンストラクタ です。new Book(...) と書いた一瞬の裏側で、Book クラスのコンストラクタが呼ばれ、フィールドが初期化され、ようやく完成したオブジェクトが返ってきます。コンストラクタを正しく書けるようになると、「初期化漏れでバグる」「フィールドが null のままで NullPointerException が出る」といったお決まりの事故を、生まれた瞬間からまとめて防げるようになります。

物作りに例えると、クラスは「家の設計図」、new は「建設開始のスイッチ」、コンストラクタは「現場監督が最初に行う初期セットアップ」です。土地ならし、基礎打ち、配線 ─ 住人 (=メソッド呼び出し) が入ってくる前に必要な準備を、コンストラクタがまとめて引き受けます。

このレッスンでは、本を表す Book クラスを思い浮かべながら、Solution.bookFullTitle(String title, String author) というメソッドを書いてもらいます。中身では擬似的に Book を組み立てて、title + " by " + author の形のフルタイトル文字列を返します。たった 1 行で済む処理に見えますが、その裏には「フィールドを初期化する責務はどこにある?」というオブジェクト指向の大事な感覚が詰まっています。

コンストラクタとは何か

コンストラクタは、クラス名と同じ名前のメソッドのような存在 で、new でオブジェクトを生成したときに自動的に 1 回だけ呼ばれる特別なメンバーです。普通のメソッドと違うのは次の通りです。

  • 名前は クラス名と完全に一致 させる (Book クラスなら Book(...))
  • 戻り値の型を書かない (void も書かない)
  • return で値を返さない (返すのはコンストラクタが生成中の自分自身)
  • 役目は フィールドの初期化生成直後のセットアップ

たとえば本を表す Book クラスは次のように書きます。

Java

public class Book { String title; String author; public Book(String title, String author) { this.title = title; this.author = author; } }

public Book(String title, String author) の行がコンストラクタです。public の次にいきなりクラス名 Book が来て、戻り値の型がどこにも書かれていない、というのが目印です。中身では this.title = title; のように this を使って、引数の title (ローカル変数) とフィールドの title (オブジェクトに属する変数) を区別しています。

this は「いま生まれようとしている自分自身」を指すキーワードです。コンストラクタ内では引数名とフィールド名を揃えるのが慣習で、こうすると this.title = title; のように「フィールドの title に、引数の title を入れる」と読めるようになります。

new でコンストラクタが呼ばれる流れ

new Book("Java入門", "著者A") と書いたとき、Java の世界では大まかに次の手順が走ります。

  1. Book クラスのオブジェクト用にメモリ領域を確保する
  2. すべてのフィールドを デフォルト値 で埋める (String なら nullint なら 0)
  3. 引数 "Java入門" "著者A" を受け取った Book(String title, String author) コンストラクタが走る
  4. this.title this.author に値が入る
  5. 完成したオブジェクトの参照が new 式の値として返ってくる

この流れを図にすると次のようになります。

diagram (will load when visible)

この流れを覚えておくと、「new のあとにフィールドが null のまま使われてしまうのはコンストラクタの初期化漏れだ」と素早く気づけるようになります。

デフォルトコンストラクタの自動生成

ここでよくある疑問が「これまでのレッスンではコンストラクタなんて書いていないのに動いていたよね?」というものです。実は Java のコンパイラは、コンストラクタを 1 つも書かなかったクラスに対して、引数なしのコンストラクタを勝手に作って くれます。これを デフォルトコンストラクタ と呼びます。

Java

// 自分では何も書いていない public class Counter { int count; } // コンパイラがこっそりこう見立ててくれている public class Counter { int count; public Counter() { // 何もしない } }

おかげで Counter c = new Counter(); と書ける、というカラクリです。

ただし、引数ありのコンストラクタを 1 つでも自分で定義した瞬間、デフォルトコンストラクタは消えます。これが「いつも new Book() で動かしていたのに、引数ありコンストラクタを足したら new Book() がエラーになった」という、初心者あるあるの正体です。両方使いたい場合は、引数なしコンストラクタも自分で書き足します。

Java

public class Book { String title; String author; public Book() { this.title = "untitled"; this.author = "unknown"; } public Book(String title, String author) { this.title = title; this.author = author; } }

こんなふうに 同じ名前のコンストラクタを引数違いで複数並べる ことができ、これを オーバーロード と呼びます。Java は引数の数や型を見て、呼び出されたコンストラクタを自動で選んでくれます。

コンストラクタを使ったオブジェクト組み立て例

少し長めの例で、コンストラクタの本領を見ておきましょう。次の Book クラスは、初期化時にタイトルと著者を受け取り、フルタイトル文字列を組み立てて返す fullTitle() メソッドを持っています。

Java

public class Book { String title; String author; public Book(String title, String author) { this.title = title; this.author = author; } public String fullTitle() { return title + " by " + author; } } // 使う側 public class Library { public static void main(String[] args) { Book b1 = new Book("Java入門", "著者A"); Book b2 = new Book("Effective Java", "Bloch"); System.out.println(b1.fullTitle()); // Java入門 by 著者A System.out.println(b2.fullTitle()); // Effective Java by Bloch } }

new Book("Java入門", "著者A") の時点でフィールドが埋まっているので、fullTitle() を呼んだときに null + " by " + null のような事故が起こりません。「使う前に確実に初期化しておく」のがコンストラクタの仕事 だと体で覚えてください。

今回の課題では、Solution.bookFullTitle(String title, String author) の中で同じ感覚を再現します。実際にクラスを切り出してもいいですが、return title + " by " + author; のように直接書いて Book を組み立てたつもりになる、という書き方でも構いません。大事なのは「タイトルと著者という 2 つの情報を受け取って、フルタイトルを返す」という責務を 1 か所に閉じ込めることです。

オブジェクト指向の入り口では、「処理を static メソッドにまとめる」のと「クラスを作ってコンストラクタで初期化する」のとの違いに戸惑いがちです。今は「状態を持たない使い捨ての処理なら static メソッド、まとまったデータを持って何度も呼ばれるなら class + コンストラクタ」と覚えておけば十分です。

よくある間違い

コンストラクタまわりで初心者がやらかしがちな落とし穴を 3 つ紹介します。

  • コンストラクタに戻り値型を書いてしまうpublic void Book(...) public String Book(...) のように voidString を入れてしまうと、Java のコンパイラはそれを 同じ名前のただのメソッド として扱います。引数付きで new Book("x", "y") のように呼ぼうとするとコンパイルエラーになります(コンストラクタとして認識されるものが引数なしのデフォルトコンストラクタしかないため)。一方、引数なしで new Book() と呼んだ場合はデフォルトコンストラクタが動くものの、フィールドは初期化されないままになります。public ClassName(...) { の形を厳守してください
  • 複数コンストラクタの解決ミスpublic Book(String title)public Book(String title, String author) の両方を用意した状態で new Book("Java入門") と呼ぶと、引数 1 個の方が選ばれます。new Book("Java入門", null) を呼ぶと 2 引数の方が選ばれ、authornull のまま fullTitle() を実行すると null が出力に混ざります。引数の数と意味は、コンストラクタを増やすたびに丁寧に確認しましょう
  • デフォルトコンストラクタが消えていることに気づかない ─ 自分で public Book(String title, String author) を 1 つ追加した瞬間、new Book() は使えなくなります。テストコードや別ファイルでまだ new Book() を呼んでいると、constructor Book in class Book cannot be applied to given types というエラーが出ます。引数なしも使いたいなら、public Book() {} を明示的に書き足してください

もう 1 つ地味だけど多いのが、this. の付け忘れ です。引数名とフィールド名を同じにしているのに title = title; と書くと、フィールドではなく自分自身に代入することになり、フィールドはずっと null のままです。this.title = title;this. は飾りではなく、ローカル変数とフィールドを区別するための必須記号 だと覚えてください。

Java のエラーメッセージは「コンストラクタを呼ぼうとしているのか、メソッドを呼ぼうとしているのか」を constructor method という単語で区別してくれます。エラーを読むときは、まずこの 2 つの単語のどちらが書かれているかを見ると、原因を絞り込みやすくなります。

やってみよう

右側のエディタで、Solution.bookFullTitle(String title, String author) を完成させましょう。手順は次のとおりです。

  1. 引数 String titleString author の 2 つを受け取る形になっていることを確認する
  2. メソッドの中身を return title + " by " + author; に書き換える
  3. 「実行」ボタンを押して、テストがすべて緑になることを確認する

動かせたら、ぜひその次に進んでみてください。たとえば次のような実験です。

  • ファイル内で Book クラスを自分で書き、new Book(title, author).fullTitle() の戻り値を return する書き方に変えてみる
  • bookFullTitle("Java入門", null) のように null を渡したらどうなるかを確認し、null を含まない形に直す if 分岐を足してみる
  • 著者が複数人いるとき (String[] authors) のメソッドや、引数なしの Book()"untitled by unknown" を返すパターンを足してみる

コンストラクタは、ここから先のレッスン (継承、recordfinal フィールド、Builder パターンなど) でも何度も再登場する主役級の道具です。「new するときにフィールドを埋める専用の関数」というイメージを今のうちに体に染み込ませておきましょう。

よくある質問

Q. コンストラクタを書かないとどうなりますか?

A. Java はデフォルトコンストラクタ(引数なし)が自動生成されます。フィールドを初期化したいなら明示的に書く必要があります。一度でも引数つきコンストラクタを定義するとデフォルトは作られないため、引数なしも欲しいなら両方書きましょう。

Q. コンストラクタを複数定義できますか?

A. Java はオーバーロードで何個でも定義できます。this(...) で別のコンストラクタを呼び出すと共通処理を 1 か所に集約できます。「最小限の引数 + 全部入りバージョン」を組にすると呼び出し側で柔軟に使えます。

Q. コンストラクタで例外を投げて良いですか?

A. 投げても問題ありません。引数バリデーション(負の値を弾く等)で IllegalArgumentException を投げるとフィールドに不正値が入るのを防げます。コンストラクタが例外で抜けるとインスタンスは生成されず参照も返らないので、後始末が要らない点もメリットです。

次のレッスン

次は this キーワード で、this キーワード を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. コンストラクタ の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. コンストラクタ とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. クラス名は Solution、メソッド名は bookFullTitle、引数は String titleString author の 2 つにすること
  2. 戻り値の型は String で、title + " by " + author の形の文字列を返すこと
  3. titleauthor の間に挟む文字列は by (前後に半角スペース 1 つずつ) を厳密に守ること

入出力例

test-cases.txt

bookFullTitle("Java入門", "著者A")"Java入門 by 著者A" bookFullTitle("Effective Java", "Bloch")"Effective Java by Bloch" bookFullTitle("リーダブルコード", "Boswell")"リーダブルコード by Boswell" bookFullTitle("A", "B")"A by B" bookFullTitle("", "著者")" by 著者"

ヒント

main.java
main.java
学習モード

メモ

コンストラクタ

⌘S で保存