PR

Java:コンストラクタとは?1分でわかりやすく解説【初心者向け】

Java

Javaではクラスからインスタンス(参考 クラスとインスタンスの基本)を生成する際に呼び出される特別なメソッドが存在します。これが「コンストラクタ(constructor)」です。

通常「メソッド」と呼ぶときには「戻り値の型」「メソッド名」「パラメータリスト」などが存在しますが、コンストラクタの場合は少し違った定義(①戻り値の型を指定しない、②クラス名と同じ名前で宣言すること、など)があります。

public class Person {
    String name;

    // コンストラクタ
    public Person(String name) {
        this.name = name;
    }
    
    void greet() {
        System.out.println("こんにちは、" + name + "さん!");
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person("太郎"); // ← ここでコンストラクタが呼ばれる
        p.greet(); // 出力:こんにちは、太郎さん!
    }
}

Javaはクラスを基本としたプログラミング言語で、かつクラスをインスタンス化する際には必ずこのコンストラクタが呼び出される仕組み。そのため、コンストラクタの基礎知識の理解は必須です。是非最後までご覧ください。

スポンサーリンク

Javaのコンストラクタとは?

コンストラクタ(constructor)とは、オブジェクトを生成するときに呼び出される特別なメソッドです。クラスのインスタンスを初期化するために使用され、通常フィールドの初期値を設定する役割を持ちます。

コンストラクタの特徴

  1. クラスと同じ名前を持つ
    → コンストラクタ名はクラス名と同じにする必要がある。
  2. 戻り値を持たない(void も書かない)
    → メソッドのように voidint などの戻り値を指定しない。
  3. new キーワードとともに呼び出される
    new を使ってオブジェクトを作成すると、自動的にコンストラクタが実行される。

コンストラクタの構文ルール

class クラス名 {
    // コンストラクタ
    クラス名() {
        // 初期化処理
    }
}

例えば、Personというクラスを考えてみましょう。以下のように、名前と年齢をフィールドとして持つクラスを作るとします。

public class Person {
    private String name;
    private int age;
    
    // コンストラクタ
    public Person() {
        // 何も設定しないデフォルトコンストラクタ
        System.out.println("Personのインスタンスが生成されました。");
    }
}

この例ではコンストラクタが明示的に定義されています。しかし、もしコンストラクタをひとつも書かなかったとしても、Javaコンパイラによって引数なしのデフォルトコンストラクタが自動生成されます。

コンストラクタの定義を省略した場合どうなる?

Javaのクラスには必ず1つ以上のコンストラクタが必要です。しかし、プログラマーが1つも定義しなかった場合、Javaコンパイラが引数のないコンストラクタを自動生成します。これをデフォルトコンストラクタと呼びます。(ページ後半で再度詳しく解説します。)

サンプルコード コンストラクタを自分で書いた場合

class Sample {
    Sample() {
        System.out.println("これは明示的なコンストラクタです");
    }
}

public class Main {
    public static void main(String[] args) {
        new Sample(); // 出力:これは明示的なコンストラクタです
    }
}

サンプルコード コンストラクタを書かなかった場合(デフォルトコンストラクタが生成される)

class Sample {
    // コンストラクタの記述なし(デフォルトコンストラクタが自動生成)
}

public class Main {
    public static void main(String[] args) {
        new Sample(); // エラーなくインスタンス生成可能(出力はなし)
    }
}

サンプルコード 引数つきコンストラクタを記述するとデフォルトは生成されない例(重要)

class Sample {
    Sample(String msg) {
        System.out.println(msg);
    }
}

public class Main {
    public static void main(String[] args) {
        // new Sample(); ←これはエラーになる(デフォルトコンストラクタが無いため)
        new Sample("引数あり"); // こちらはOK
    }
}

メソッドとの違い

一見、コンストラクタはメソッドのようにも見えますが、厳密にはメソッドではありません。

ポイント コンストラクタとメソッドの違いまとめ

項目コンストラクタメソッド
名前クラス名と同じ自由に名付けられる
戻り値の型なし(voidすら書かない)必ず戻り値の型を書く
呼び出しタイミングnewでインスタンス生成時に自動で実行自分で呼び出す
目的初期化処理動作・処理を定義する

最も大きな違いは「クラス名と同じ名称」で「戻り値を持たない(voidすら書かない)」という点です。また、オブジェクト生成時にしか呼ばれないという点も大きな違いといえます。

また、Javaの仕様上コンストラクタは「constructor(構築子)」という別の構文ルールで定義されています。
見た目が似ているため「特別なメソッド」と表現されることもありますが、メソッドではありません。初心者向けにざっくり説明する場合は「特別なメソッド」と言ってもOKですが、正確には「クラスの初期化専用の構文」だと理解しましょう。

可視性(アクセス修飾子)について

コンストラクタには、他のメソッド同様にpublicprotectedprivateなどのアクセス修飾子を付与することができます。クラス外部からインスタンス化したい場合はpublicにし、継承関係の中でしか使わないようにしたい場合はprotectedにするといった制御が可能です。特別なケースとして、シングルトンパターンなど「外部からインスタンス化を禁止したい」という場合は、privateコンストラクタを使って実現します。

デフォルトコンストラクタ

デフォルトコンストラクタとは、プログラマーが明示的に1つもコンストラクタを書かなかった場合に、Javaコンパイラが自動生成する『引数なしのコンストラクタ』のことです。

クラス名() {
    // 中身は空(何もしない)
}

クラスには最低1つのコンストラクタが必須ですが、もし自分でコンストラクタを書かない場合、Javaは↑を自動生成します。ただし、1つでも自分でコンストラクタを記述した瞬間に、デフォルトコンストラクタは生成されなくなります。

「デフォルトコンストラクタ」と「super()」

デフォルトコンストラクタは基本的に処理なしのコンストラクタでした。ただし「継承」が絡むと、実は自動で親のコンストラクタ(super)を呼び出すというように動きを変えます。

class Parent {
    Parent() {
        System.out.println("Parentのコンストラクタ");
    }
}

class Child extends Parent {
    // コンストラクタなし(デフォルトコンストラクタ生成)
}

public class Main {
    public static void main(String[] args) {
        new Child();
    }
}

このとき、Childにはコンストラクタがないため、自動で次のようなコンストラクタが生成されます。子クラスのコンストラクタが呼ばれると、先頭にある super(); によって親クラスのコンストラクタが呼ばれます

Child() {
    super(); //←ここがポイント(自動挿入)
}

まとめると・・・

  • デフォルトコンストラクタ=基本は空の処理。
  • 継承が絡むと自動で親のコンストラクタ呼び出しが追加される。
    これが「デフォルトコンストラクタ」と「super」の関係です。

引数つきコンストラクタ

引数を受け取るコンストラクタを定義すると、コンストラクタの多様性が広がります。例えば、Personクラスに「名前と年齢を設定したい」という要望があれば、次のように引数つきコンストラクタを定義できます。

public class Person {
    private String name;
    private int age;

    // 引数なしコンストラクタ
    public Person() {
        // デフォルトでは名前も年齢も未設定
        System.out.println("デフォルトコンストラクタが呼ばれました。");
    }

    // 引数つきコンストラクタ
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("引数つきコンストラクタが呼ばれました。");
    }
}

this.name = name;this.age = age; のように、引数によって渡された値をフィールドに代入することで、初期値を自由に設定できます。このように、必要に応じて複数のコンストラクタを用意することを「コンストラクタのオーバーロード」と呼びます。

コンストラクタのオーバーロード

メソッドと同様に定義可能

Javaではメソッドと同様に、コンストラクタでもオーバーロードを行えます。オーバーロードの条件は「パラメータリストが異なること」です。つまり、引数の数や型が異なるコンストラクタを複数用意できます。

用途に合わせた初期化ロジック

コンストラクタをオーバーロードする最大のメリットは、用途に応じて初期化ロジックを使い分けられることです。たとえば、文字列だけを引数に取りたい、あるいは年齢だけを指定したい、といったときに複数のパターンを定義しておくと便利です。

public Person(String name) {
    this.name = name;
    this.age = 0;  // 初期値
}

public Person(int age) {
    this.name = "名無し";
    this.age = age;
}

こうすることで、名前のみを指定したい場合、年齢のみを指定したい場合など、オブジェクト生成時のインタフェースを柔軟に設計できます。実際の業務シーンでも、「最低限必要な情報だけ指定すればいい」「追加情報も同時に与えられる」などの形で、コンストラクタのオーバーロードがしばしば活用されます。

コンストラクタチェーン(this() の呼び出し)と super()

this() の呼び出し

コンストラクタ内では、同じクラスの別コンストラクタを呼び出すことができます。これを「コンストラクタチェーン」と呼び、通常はthis()を使って呼び出します。

public Person() {
    this("名無し", 0);  // 同じクラスの別コンストラクタを呼び出し
    System.out.println("デフォルトコンストラクタ(チェーン)");
}

public Person(String name, int age) {
    this.name = name;
    this.age = age;
    System.out.println("引数つきコンストラクタ");
}

この例では、引数なしのコンストラクタの中から、引数つきコンストラクタを呼んでいます。結果としてPerson()が呼ばれたときでも、実際にはPerson(String name, int age)の処理を共通化できるため、初期化ロジックが重複するのを防ぐことができます。

ただし、this()呼び出しはコンストラクタの先頭でしか行えないというルールがあります。これはJavaの言語仕様上の制限であり、一度でもフィールドを設定した後にthis()で別コンストラクタを呼び出すことはできません。この点は注意が必要です。

super() の呼び出し

継承関係にあるクラスでは、子クラスのコンストラクタから親クラスのコンストラクタを呼び出すことができます。Javaでは暗黙的にsuper()が呼ばれていますが、必要に応じて明示的にsuper(引数リスト)を書くことも可能です。たとえば、親クラスのコンストラクタに引数が必要な場合などが典型例です。

/* 親クラス
public class Person {
    private String name;
    private int age;

    // 引数つきコンストラクタ
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("引数つきコンストラクタが呼ばれました。");
    }
}
*/

public class Student extends Person {
    private String studentId;

    public Student(String name, int age, String studentId) {
        super(name, age);      // 親クラスのPerson(String,int)を呼び出し
        this.studentId = studentId;
    }
}

このようにして、子クラス側でフィールドを追加したいとき、親クラスの初期化を合わせて実行できます。これも「オブジェクト生成には親クラスの初期化が欠かせない」という大原則に基づく仕様です。

コンストラクタでの初期化と初期値代入の使い分け

クラス内でフィールドを宣言するときに、次のようにあらかじめ初期値を代入しておく方法があります。

public class Sample {
    private int number = 10;
    private String text = "Hello";
}

この書き方もコンストラクタと同様に「フィールドの初期化」を行っていますが、どちらを使うべきか迷うケースもあるでしょう。

→基本的には「全インスタンスに共通して同じ初期値を与えたい場合」はフィールド初期化子を使い、「パラメータによって異なる値を設定したい場合」や「複数のフィールドに一貫したロジックで初期化を行いたい場合」はコンストラクタを使うと整理できます。

コンストラクタとイニシャライザブロック(初期化子)

Javaには「イニシャライザブロック(初期化子)」と呼ばれる、クラス定義内に直接記述するブロックがあります。具体的には、以下のように波括弧 {} だけで囲まれた部分です。このイニシャライザブロックには大きく分けて次の2種類があります。

{
    // インスタンスイニシャライザ
}

1.インスタンスイニシャライザ

クラス内に static キーワードなしで書かれたブロック({ ... })は、「インスタンスイニシャライザ」として機能します。これはインスタンスが生成されるタイミングで実行されるコードブロックです。具体的には、コンストラクタが呼び出される前後(正確には、親クラスのコンストラクタ呼び出しが終わった直後)にこのブロックのコードが順番に実行されます。たとえば次のようなクラスがあるとき、

public class Sample {
    private int number;

    // インスタンスイニシャライザ
    {
        System.out.println("インスタンスイニシャライザが呼ばれました");
        number = 10;
    }

    // コンストラクタ
    public Sample() {
        System.out.println("コンストラクタが呼ばれました");
    }
}

// インスタンスイニシャライザが呼ばれました
// コンストラクタが呼ばれました

↑のクラスのコンストラクタを呼び出してインスタンスを生成したときの処理順は↓。

  1. 親クラス(Objectクラス)のコンストラクタ呼び出し
  2. インスタンスイニシャライザの実行(上記の{ ... }ブロック)
  3. 現在のクラスのコンストラクタ本体(Sample())の実行

インスタンスイニシャライザで行う処理は、通常はコンストラクタ内にまとめて書いても機能上の違いはありません。そのため、明示的にコンストラクタを複数用意している場合などは、イニシャライザよりコンストラクタにまとめて記述した方が可読性が高くなる場合も多いです。一方、

  • 「どのコンストラクタから呼び出しても必ず実行させたい初期化処理があるが、それをひとまとまりのブロックとして見やすく分離したい」
  • 「フィールド初期化子だけでは書ききれない少し複雑な初期化ロジックを、コンストラクタの引数設定とは独立にまとめておきたい」

といったケースではインスタンスイニシャライザも有効に活用できます。なお、複数のインスタンスイニシャライザをクラスに記述した場合は、ソースコード上で記述した順番に実行されます。

2.staticイニシャライザ(静的イニシャライザ)

もう一つ、ブロックの先頭に static をつけると「staticイニシャライザ(静的イニシャライザ)」になります。これはクラスがロードされるタイミングで一度だけ実行されるコードブロックです。たとえば、

public class StaticSample {
    private static int count;

    static {
        System.out.println("静的イニシャライザが呼ばれました");
        count = 100;
    }

    public StaticSample() {
        System.out.println("コンストラクタが呼ばれました");
    }
}

このクラスを初めて使用するとき(JVMがこのクラスをロードした瞬間)に、static { ... } ブロック内のコードが一度だけ実行されます。典型的な用途は、

  • staticなフィールドの初期値を複雑な計算や外部リソースを参照して設定したい
  • ログ設定など、一度だけ行えばよい共通的な初期化作業をまとめて書きたい

といった場面です。ただし、静的イニシャライザに大量のロジックを詰め込みすぎると、クラスロード時に大きな負担がかかることになるので注意が必要です。

ポイント コンストラクタと初期化子(イニシャライザ)

  • インスタンスイニシャライザ { ... }
    各インスタンス生成時にコンストラクタより先行して(親クラスのコンストラクタ呼び出し後、子クラスのコンストラクタ本体の前)実行される。すべてのコンストラクタに共通して必要な初期化ロジックをまとめたいときなどに使う。
  • staticイニシャライザ static { ... }
    クラスがロードされるタイミングで一度だけ実行される。静的フィールドの初期化などに使用。

通常はコンストラクタやフィールド初期化子を使うだけでも十分なケースが多いですが、複数のコンストラクタにまたがる共通処理をシンプルに書きたい、あるいは複雑なstaticな初期設定をまとめたい場合にイニシャライザブロックは有効な手段となります。

アクセス制御とコンストラクタ

public, protected, private の使い分け

前述のとおり、コンストラクタにもアクセス修飾子を付けることができます。通常はpublicで定義してどこからでもインスタンス化できるようにしますが、特定の目的で制限をかけたい場合には他の修飾子を検討します。

  • public:どこからでもインスタンス生成可能
  • protected:同一パッケージ内、もしくはサブクラスからのみインスタンス生成可能
  • private:クラス内からのみインスタンス生成可能(シングルトンやユーティリティクラスで活用)

シングルトンパターンの例

シングルトンパターンとは、あるクラスのインスタンスをただ一つだけに制限するデザインパターンです。これをJavaで実現する代表的な方法は、コンストラクタをprivateにし、クラス内部で唯一のインスタンスを静的メンバとして保持する方法です。

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
        // 外部からインスタンス生成不可
    }

    public static Singleton getInstance() {
        return instance;
    }
}

こうすることで、クラス外部からはnew Singleton()でインスタンスを生成できなくなり、常にgetInstance()メソッドを通して同じインスタンスを返す仕組みを作ることができます。

コンストラクタが持つ本質的な役割

オブジェクト指向の「誕生時の一貫性」

オブジェクト指向設計において重要なポイントは「オブジェクトは常に正しい状態で生成されるべき」という一貫性です。もし、インスタンス化時にフィールドが正しく初期化されずにあやふやな状態であったり、外部リソースを必須としているのに確保されていなかったりすると、後続の処理で想定外の不具合が起こり得ます。

コンストラクタを活用すれば、「このオブジェクトを作るときには最低限これだけの情報やリソースが必要だ」ということを明確に示しながら、正しく初期化を行えます。

他のメソッドとの連携

コンストラクタそのものはオブジェクト生成時にのみ呼ばれる特殊なメソッドですが、実際には他の通常メソッドや、フィールド初期化子などと連携して「オブジェクトにとって必要な準備をすべて行う」ために存在します。特に大規模開発では、コンストラクタで呼び出されるメソッドがさらに例外を投げる可能性があるなど、複雑なシーケンスになる場合もあります。そうした場合でも「コンストラクタが最終的な入口」になっていることを意識すると、コードの整理がしやすくなるでしょう。

「new」との関連

Javaでインスタンスを生成する際にはnew演算子を使います。このnew演算子が呼ばれたタイミングで、メモリ上に新たなオブジェクトの領域が確保されます。その後、コンストラクタが呼び出されて初期化が行われ、最終的に生成されたオブジェクトの参照が戻り値として返されるという流れです。

C++のようにメモリ管理を手動で行う必要はなく、Javaはガーベジコレクションによって不要になったオブジェクトを回収する仕組みを備えています。しかし、その分コンストラクタの役割として「生成時にやるべきことをきちんとやる」必要性がいっそう際立っているといえます。

コンストラクタで気をつけるべきポイント

例外処理

コンストラクタも例外処理の対象になります

もしコンストラクタ内部で何らかのエラーが起こった場合、コンストラクタが例外をスローすると、そのインスタンスの生成自体が失敗します。これは通常のメソッドと同様にtry-catchで扱うことができますが、「コンストラクタ内で重い処理や不確定要素の多い処理をできるだけ避ける」というのも一つの設計指針です。あまりに複雑な初期化は、静的初期化ブロックやファクトリーメソッドに分離した方がメンテナンス性が高まるケースもあります。

継承時の互換性

継承関係において、親クラスが引数なしのコンストラクタしか持たない(あるいはデフォルトコンストラクタしか定義していない)場合、子クラス側は自動的にsuper()が呼ばれます。しかし、もし親クラスが引数付きコンストラクタのみを提供している場合は、子クラス側でも明示的にsuper(必要な引数...)を呼び出す必要があります。この点を見落とすとコンパイルエラーが発生します。初心者が陥りやすいミスなので、コンストラクタを定義する際には「継承も見越した設計」を意識するのがよいでしょう。

循環呼び出しの禁止

this()super()を使ったコンストラクタチェーンは便利ですが、誤って循環呼び出しを行わないように注意しましょう。たとえば、コンストラクタAthis()コンストラクタBを呼び、コンストラクタBがまたthis()コンストラクタAを呼んでしまうと、無限ループに陥りコンパイルエラーが起こります。Javaコンパイラはこの種のエラーを検出してくれますが、意図をしっかり把握していないと混乱の原因になりがちです。

まとめ Javaのコンストラクタとは?

  1. コンストラクタとは
    • オブジェクト生成時に必ず呼ばれる、クラス名と同じ名前を持つ特別な“メソッド”
    • 戻り値の型を持たず、new演算子によるインスタンス化の流れに組み込まれている
  2. 基本構文とデフォルトコンストラクタ
    • クラスにコンストラクタをまったく定義しない場合でも、引数なしのデフォルトコンストラクタが自動生成される
    • 引数付きコンストラクタを複数定義することでオーバーロードが可能
  3. コンストラクタチェーン(this())とsuper()
    • 同一クラス内の別コンストラクタを呼び出すthis()
    • 親クラスのコンストラクタを呼び出すsuper()
    • どちらもコンストラクタの先頭でしか呼び出せない
  4. オブジェクト指向におけるコンストラクタの意義
    • オブジェクトの初期状態を保証し、正しい生成を担保する
    • 複雑な初期化ロジックや例外処理、継承との連携を管理する
  5. アクセス制御と応用
    • public, protected, privateを使い分けてインスタンス生成の自由度を制御
    • シングルトンやファクトリーメソッド、ビルダーパターンなどで応用可能
タイトルとURLをコピーしました