15.

Javaコンストラクタ完全ガイド(this/super/初期化順序/Builder/record)

編集
この記事の要点
  • コンストラクタ = クラス名と同じ名前のメソッドで、new 時に呼び出されオブジェクトを初期化する
  • 引数違いでオーバーロード可能。this(...) で別コンストラクタ呼び出し、super(...) で親クラス呼び出し
  • 初期化順序: static フィールド/static 初期化子 → インスタンスフィールド/初期化子 → コンストラクタ本体
  • コンストラクタ未定義時は引数なしのデフォルトコンストラクタが自動生成される
  • private コンストラクタで Singleton / Factory / Utility パターンを実現
  • final フィールドはコンストラクタ完了時までに必ず初期化、不変オブジェクト設計の基本
  • 引数が多い場合は Builder パターン、Java 14+ なら record でボイラープレート削減

コンストラクタの基本構文

コンストラクタはクラス名と同名の特殊メソッドで、戻り値の型を持ちません。new ClassName(...) したときに自動的に呼び出され、オブジェクトの初期状態を作ります。

public class User {
    private final String name;
    private final int age;

    // コンストラクタ(引数なし版)
    public User() {
        this.name = "anonymous";
        this.age = 0;
    }

    // コンストラクタ(引数あり版)
    public User(String name, int age) {
        if (age < 0) throw new IllegalArgumentException("age must be >= 0");
        this.name = name;
        this.age = age;
    }
}

// 使用
User u1 = new User();
User u2 = new User("Alice", 30);

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

コンストラクタを 1 つも書かなかった場合、コンパイラが引数なしの public コンストラクタを自動生成します。1 つでも書いた場合は自動生成されません。

// コンストラクタ未定義 → デフォルトが暗黙に生成
public class A {
    int x;
}
A a = new A(); // OK

// 引数付きを書いたらデフォルトは消える
public class B {
    int x;
    public B(int x) { this.x = x; }
}
B b1 = new B();    // コンパイルエラー
B b2 = new B(10);  // OK

this() と super() による委譲

同じクラスの別コンストラクタは this(...)、親クラスのコンストラクタは super(...) で呼び出します。どちらもコンストラクタの最初の文でなければなりません。

public class Rectangle {
    private final int width;
    private final int height;

    public Rectangle() {
        this(1, 1);  // ← this() で別コンストラクタへ委譲
    }

    public Rectangle(int side) {
        this(side, side);  // 正方形
    }

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
}

// 親クラスを呼ぶ場合
public class Square extends Rectangle {
    public Square(int side) {
        super(side, side);  // ← super() で親へ
    }
}

明示的に super(...) を書かないと、コンパイラが暗黙に super() (引数なし)を挿入します。親に引数なしコンストラクタが無いとコンパイルエラーになります。

初期化順序

順序処理
1親クラスの static フィールド初期化 / static 初期化子(初回ロード時のみ)
2子クラスの static フィールド初期化 / static 初期化子(初回ロード時のみ)
3親クラスのインスタンスフィールド初期化 / インスタンス初期化子
4親クラスのコンストラクタ本体
5子クラスのインスタンスフィールド初期化 / インスタンス初期化子
6子クラスのコンストラクタ本体
class Parent {
    static { System.out.println("1. Parent static"); }
    { System.out.println("3. Parent instance init"); }
    Parent() { System.out.println("4. Parent constructor"); }
}
class Child extends Parent {
    static { System.out.println("2. Child static"); }
    { System.out.println("5. Child instance init"); }
    Child() { System.out.println("6. Child constructor"); }
}

new Child();
// 1. Parent static
// 2. Child static
// 3. Parent instance init
// 4. Parent constructor
// 5. Child instance init
// 6. Child constructor

private コンストラクタ(Singleton)

private にすると外部から new できません。Singleton や Utility クラスで使います。

// Singleton パターン
public class Config {
    private static final Config INSTANCE = new Config();
    private Config() {}  // 外部 new 禁止
    public static Config getInstance() { return INSTANCE; }
}

// Utility クラス(インスタンス化禁止)
public final class StringUtils {
    private StringUtils() {
        throw new AssertionError("インスタンス化禁止");
    }
    public static String reverse(String s) { /*...*/ }
}

final フィールドとコンストラクタ

final フィールドはコンストラクタ完了時までに必ず一度だけ代入されなければなりません。後から変更不可なので、不変オブジェクト(Immutable)の基本パターンです。

public final class Money {
    private final long amount;
    private final String currency;

    public Money(long amount, String currency) {
        this.amount = amount;
        this.currency = currency;
        // ここで return すると final 未初期化でコンパイルエラー
    }
    // setter は提供しない → 不変
    public long getAmount() { return amount; }
    public String getCurrency() { return currency; }
}

Builder パターン

引数が多くなったときは Builder で読みやすく組み立てます。

public final class Pizza {
    private final String size;
    private final boolean cheese;
    private final boolean pepperoni;
    private final boolean mushroom;

    private Pizza(Builder b) {
        this.size = b.size;
        this.cheese = b.cheese;
        this.pepperoni = b.pepperoni;
        this.mushroom = b.mushroom;
    }

    public static class Builder {
        private String size;
        private boolean cheese, pepperoni, mushroom;
        public Builder size(String s) { this.size = s; return this; }
        public Builder cheese(boolean v) { this.cheese = v; return this; }
        public Builder pepperoni(boolean v) { this.pepperoni = v; return this; }
        public Builder mushroom(boolean v) { this.mushroom = v; return this; }
        public Pizza build() { return new Pizza(this); }
    }
}

Pizza p = new Pizza.Builder()
    .size("L").cheese(true).pepperoni(true).build();

record(Java 14+)

不変データクラスは record で 1 行になります。コンストラクタ・getter・equals・hashCode・toString が自動生成されます。

// 従来 50 行 → 1 行
public record User(String name, int age) {}

// バリデーションは compact constructor で
public record User(String name, int age) {
    public User {
        if (age < 0) throw new IllegalArgumentException();
        if (name == null) throw new NullPointerException();
    }
}

User u = new User("Alice", 30);
System.out.println(u.name());  // Alice
System.out.println(u);          // User[name=Alice, age=30]

FAQ

Q: コンストラクタから this をリークしてはいけないのはなぜ?
A: 完全初期化前のオブジェクトを別スレッドや外部に渡すと、final フィールドが未初期化状態で観測される可能性があるためです。

Q: コンストラクタで例外を投げてもよい?
A: 投げて OK。途中で例外が出るとオブジェクトは作られず、GC 対象になります。リソースを掴んだ後の例外には注意(リーク対策に try-catch でクローズ)。

Q: 静的ファクトリメソッドとコンストラクタどちらが良い?
A: 名前を付けたい / 既存インスタンスを返したい / サブタイプを返したい場合は静的ファクトリ(List.of() など)。それ以外はコンストラクタで十分です。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本事項
  2. HTMLへの埋め込み
  3. 変数
  4. 可変変数
  5. 定数
  6. データ型
  7. キャスト
  8. エスケープ文字
  9. 配列
  10. 演算子
  11. 代入の際の注意点
  12. 条件分岐
  13. 繰り返し処理
  14. クラスとインスタンス
  15. コンストラクタ
  16. 関数
  17. スーパーグローバル変数
  18. スコープ
  19. staticについて
  20. yieldについて
  21. ファイルのアップロード方法
  22. DB接続方法
  23. SQL実行方法
  24. カプセル化の具体例
  25. 継承の構文
  26. オーバーライド
  27. ポリモーフィズム(多様性)の具体例
  28. 抽象クラス・メソッドの構文と具体例
  29. GET通信
  30. try catchで全てのエラーを拾う方法

最近更新/作成されたページ