Java | オブジェクト指向:デフォルトコンストラクタ

Java Java
スポンサーリンク

デフォルトコンストラクタの全体像

デフォルトコンストラクタは「引数なしのコンストラクタ」です。クラスにコンストラクタを1つも定義していない場合、Javaコンパイラが自動で用意します。役割は「そのクラスのオブジェクトを、既定の初期状態で立ち上げる」ことです。自動生成されるため、書かなくても new できますが、検証や初期値を入れたいときは自分で明示的に定義します。


いつ自動生成されるかと消える条件

暗黙の生成のルール

クラスにコンストラクタがまったく宣言されていない場合、コンパイラが自動的に「public 引数なし」のコンストラクタを1つ作ります。これにより new クラス名() が可能になります。初期化は「フィールドのデフォルト値」や「フィールド初期化式」に従って行われます。

public class Plain {
    int n;           // デフォルトは 0
    String s;        // デフォルトは null
    boolean ok;      // デフォルトは false
    // コンストラクタ未定義 → public Plain() が暗黙に生成される
}
var p = new Plain(); // OK
Java

コンストラクタを定義したら自動生成されない

1つでもコンストラクタを自分で定義したら、暗黙のデフォルトコンストラクタは生成されません。引数なしで生成したいなら、明示的に no-arg コンストラクタを定義します。

public class HasCtor {
    public HasCtor(int x) { /* ... */ }
    // 暗黙のデフォルトは消える
}
// var a = new HasCtor(); // コンパイルエラー(no-arg 不在)

public class HasCtor2 {
    public HasCtor2(int x) { /* ... */ }
    public HasCtor2() { /* 引数なしを明示 */ }
}
Java

挙動と初期化の順序(重要ポイントの深掘り)

フィールドのデフォルト値

new の直後、フィールドは型ごとの既定に初期化されます。数値は 0、boolean は false、参照は null です。これを前提に「null 安全」に設計します。

public class Defaults {
    int a;            // 0
    double r;         // 0.0
    boolean ready;    // false
    String title;     // null
}
Java

フィールド初期化式とインスタンス初期化子

フィールドに初期化式がある場合、no-arg でもその式が実行されます。複雑な初期化はコンストラクタに集約し、初期化子は最小限にとどめると読みやすくなります。

public class InitOrder {
    private final java.time.Instant createdAt = java.time.Instant.now(); // フィールド初期化式
    public InitOrder() { /* 追加の初期化があればここで */ }
}
Java

継承時の呼び出し

子クラスのデフォルトコンストラクタは、暗黙に super()(親の引数なしコンストラクタ)を呼びます。親に no-arg がない場合、子に明示的なコンストラクタを定義して super(…) で親を初期化する必要があります。

class Base {
    Base(int n) { /* 親は no-arg なし */ }
}
class Child extends Base {
    // 暗黙の Child() は super() を呼べずコンパイル不可
    public Child() { super(0); } // 必要なら自分で no-arg を定義して親へ値を渡す
}
Java

明示的に用意する理由と書き方

可視性の制御

「外部から引数なしで作れては困る」ケースでは、no-arg を private にしてインスタンス化を制限できます。テストやフレームワーク用に public にするか、ライフサイクルを守るために private にするかを設計で決めます。

public final class Utility {
    private Utility() {} // 生成禁止(名前空間クラスとして使う)
}
Java

既定値の設定・検証

引数なしでも「安全な初期状態」を保証したいなら、no-arg で既定値を入れ、検証を行います。引数ありに処理を集約し、this(...) で委譲すると重複を避けられます。

public final class User {
    private final String id;
    private final String name;

    public User() {
        this("UNKNOWN", ""); // 既定値へ委譲
    }
    public User(String id, String name) {
        if (id == null || id.isBlank()) throw new IllegalArgumentException("id");
        this.id = id;
        this.name = name == null ? "" : name.trim();
    }
}
Java

フレームワークとの相性と実務ポイント

JavaBeans・シリアライザ・ORM が好む no-arg

多くのフレームワーク(JavaBeans、JPA/Hibernate 等)は、反射でオブジェクトを生成する際に「public な引数なしコンストラクタ」を要求します。値の設定はセッターやフィールドアクセスで行う設計が前提です。ドメインモデルを不変にしたい場合は、JPA のためだけに no-arg を protected で用意するなど「公開範囲を絞る工夫」が有効です。

// JPA向け:外部からは作らせないが、フレームワークは反射で呼べる
@Entity
public class Person {
    @Id private Long id;
    private String name;

    protected Person() {}         // JPA用
    public Person(String name) {  // 業務用の安全な入口
        if (name == null || name.isBlank()) throw new IllegalArgumentException();
        this.name = name.trim();
    }
}
Java

反射・プロキシの生成要件

シリアライズやプロキシ生成では、no-arg があると扱いやすくなります。必要最小の可視性(protected/private)にして、業務コードからの乱用は防ぎます。


例題で身につける

例 1: 暗黙のデフォルトコンストラクタ

public class PlainUser {
    String name = ""; // 初期化式で安全に
}
// コンストラクタ未定義 → public PlainUser() が生成される
var u = new PlainUser();
System.out.println(u.name); // ""
Java

例 2: 他のコンストラクタ定義で消えるケース

public class OnlyArg {
    public OnlyArg(String s) {}
}
// new OnlyArg() は不可(no-arg なし)

public class WithNoArg {
    public WithNoArg(String s) {}
    public WithNoArg() { /* ここで既定の立ち上げ */ }
}
// new WithNoArg() はOK
Java

例 3: 親に no-arg がない場合の対処

class Base {
    final int n;
    Base(int n) { this.n = n; }
}
class Child extends Base {
    Child() { super(42); } // 子の no-arg から親へ既定値を渡す
}
Java

例 4: this() で既定値へ委譲する

public final class Config {
    private final String region;
    private final int timeoutMs;

    public Config() { this("ap-northeast-1", 3000); }
    public Config(String region, int timeoutMs) {
        if (timeoutMs <= 0) throw new IllegalArgumentException("timeout>0");
        this.region = region;
        this.timeoutMs = timeoutMs;
    }
}
Java

よくあるつまずきと回避

「引数ありコンストラクタを作ったら no-arg で生成できなくなった」という混乱が定番です。必要なら no-arg を明示的に追加し、既定値や委譲で一箇所のロジックへ集約してください。継承では「親に no-arg がないと、子の暗黙 no-arg は作られない」点に注意し、super(...) を必ず呼びます。フレームワーク要件で no-arg が必要でも、業務的に不正な初期状態を許したくないなら、可視性を絞る(protected/private)か、生成後に必須設定を強制する設計にします。


仕上げのアドバイス(重要部分のまとめ)

デフォルトコンストラクタは「引数なしで安全に立ち上げる入口」。クラスにコンストラクタ未定義なら自動生成されますが、1つでも定義すると消えるため、必要なら明示的に no-arg を用意する。初期化順序(フィールド既定→初期化式→親→子)を理解し、継承では super() の有無が運命を分ける。実務ではフレームワーク要件に合わせつつ、既定値・検証・可視性で「壊れない初期状態」を守る——ここを押さえれば、生成の設計で迷いません。

タイトルとURLをコピーしました