Java | オブジェクト指向:引数付きコンストラクタ

Java Java
スポンサーリンク

引数付きコンストラクタの全体像

引数付きコンストラクタは、生成時に必要な情報を受け取って「正しい初期状態」を確立するための入口です。クラス名と同名で定義し、new クラス名(必要な値…) の形で呼びます。役割は、値の受け取り・検証・フィールドへの代入を一度だけ行い、その後のメソッドが安心して動ける土台を作ることです。


基本構文と使い方

コンストラクタの定義と new の呼び出し

コンストラクタは戻り値型を書かず、クラス名と同名で定義します。生成時に渡した引数でフィールドを初期化します。

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

    public User(String id, String name) {
        if (id == null || id.isBlank()) throw new IllegalArgumentException("id required");
        this.id = id;
        this.name = normalize(name);
    }

    public String id() { return id; }
    public String name() { return name; }
    private static String normalize(String s) {
        return s == null ? "" : s.trim().replaceAll("\\s+", " ");
    }
}

// 生成
var u = new User("U-1", "  Taro  ");
System.out.println(u.name()); // "Taro"
Java

オーバーロードで初期化パターンを用意する

必要に応じて、引数の組み合わせ違いで複数のコンストラクタを定義できます。重複は this(...) で共通化します。

public final class Product {
    private final String id;
    private final int price;

    public Product(String id, int price) {
        if (id == null || id.isBlank()) throw new IllegalArgumentException("id");
        if (price < 0) throw new IllegalArgumentException("price>=0");
        this.id = id;
        this.price = price;
    }

    public Product(String id) { this(id, 0); } // 既定価格に委譲
}
Java

重要ポイントの深掘り:検証と不変性

初期状態を「ここで」保証する

引数付きコンストラクタは、契約違反の入力を拒否して整合性を守る場所です。必須値の欠落や範囲外は例外で早期に失敗させます。これにより、後続のメソッドで余計なガードを繰り返さずに済みます。

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

    public Money(int amount, String currency) {
        if (amount < 0) throw new IllegalArgumentException("amount>=0");
        if (currency == null || currency.isBlank()) throw new IllegalArgumentException("currency");
        this.amount = amount;
        this.currency = currency;
    }
}
Java

final フィールドで不変を築く

final は「コンストラクタ完了までに一度だけ代入」を保証します。不変は並行性・テストで強力な安全性をもたらします。可変が必要なら、検証付きメソッドでのみ変更を許可します。

public final class Token {
    private final String value;
    public Token(String value) {
        if (value == null || value.isBlank()) throw new IllegalArgumentException("token");
        this.value = value;
    }
    public String value() { return value; }
}
Java

継承時の初期化順序と super の必須性

親→子の順で初期化される

new の流れは「メモリ確保→フィールドの既定値→フィールド初期化式→親コンストラクタ→子コンストラクタ」。継承している場合、子のコンストラクタは必ず最初に super(...) を呼び、親の初期化を先に完了させます。

public class Person {
    protected final String name;
    public Person(String name) {
        if (name == null || name.isBlank()) throw new IllegalArgumentException("name");
        this.name = name.trim();
    }
}

public final class Employee extends Person {
    private final int grade;
    public Employee(String name, int grade) {
        super(name);                   // 親を先に初期化
        if (grade < 1 || grade > 10) throw new IllegalArgumentException("grade 1..10");
        this.grade = grade;
    }
}
Java

親に引数なしコンストラクタがない場合、super() は使えないため、必ず適切な引数で親を初期化する必要があります。


設計の選択肢:ファクトリ・ビルダー・record

static ファクトリで意図を明確にする

生成規則が複雑・名前を付けたい場合は、コンストラクタを隠してファクトリメソッドへ集約します。例外方針や前処理を一箇所にできます。

public final class Email {
    private final String value;
    private Email(String value) { this.value = value; }

    public static Email of(String raw) {
        var v = normalize(raw);
        if (!isValid(v)) throw new IllegalArgumentException("invalid email");
        return new Email(v);
    }
    private static String normalize(String s) { return s == null ? "" : s.trim().toLowerCase(); }
    private static boolean isValid(String s) { int at = s.indexOf('@'); return at > 0 && at == s.lastIndexOf('@') && at < s.length() - 1; }
}
Java

ビルダーパターンで多引数を整理する

必須・任意が混在する多引数は、読みやすさと拡張性のためにビルダーを使います。最後に build() で一括検証します。

public final class Mail {
    private final String to, subject, body;
    private Mail(String to, String subject, String body) { this.to = to; this.subject = subject; this.body = body; }

    public static class Builder {
        private String to, subject, body;
        public Builder to(String v) { this.to = v; return this; }
        public Builder subject(String v) { this.subject = v; return this; }
        public Builder body(String v) { this.body = v; return this; }
        public Mail build() {
            if (to == null || to.isBlank()) throw new IllegalStateException("to required");
            return new Mail(to, subject, body);
        }
    }
}
Java

record のコンストラクタで簡潔に検証する

値オブジェクトは record が適しています。必要なら「コンパクトコンストラクタ」で検証を足します。

public record Point(int x, int y) {
    public Point {
        if (x < 0 || y < 0) throw new IllegalArgumentException("non-negative");
    }
}
Java

例題で身につける

例 1: 設定オブジェクトの安全な生成

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

    public Config(String region, int timeoutMs) {
        if (region == null || region.isBlank()) throw new IllegalArgumentException("region");
        if (timeoutMs <= 0) throw new IllegalArgumentException("timeout>0");
        this.region = region;
        this.timeoutMs = timeoutMs;
    }
    public String region() { return region; }
    public int timeoutMs() { return timeoutMs; }
}
Java

例 2: 価格の正規化と丸め規則を統一

public final class Price {
    private final int value;
    public Price(int rawValue, double taxRate) {
        if (rawValue < 0 || taxRate < 0) throw new IllegalArgumentException();
        this.value = (int) Math.round(rawValue * (1 + taxRate));
    }
    public int value() { return value; }
}
Java

例 3: 親子初期化の連携

abstract class BaseRepo {
    protected final java.nio.file.Path root;
    public BaseRepo(java.nio.file.Path root) {
        if (root == null) throw new IllegalArgumentException("root");
        this.root = root;
    }
}
final class FileRepo extends BaseRepo {
    private final java.nio.charset.Charset cs;
    public FileRepo(java.nio.file.Path root, java.nio.charset.Charset cs) {
        super(root);
        this.cs = cs == null ? java.nio.charset.StandardCharsets.UTF_8 : cs;
    }
}
Java

よくあるつまずきと回避

引数付きコンストラクタで検証を省くと、後続のロジックで毎回ガードが必要になり、バグ温床になります。必ず入口で正すか、例外で拒否してください。継承では親の初期化忘れ(super(...) の不備)に注意し、フィールド初期化式とコンストラクタの役割が競合しないように整理します。多引数で読みづらくなったら、引数のグルーピング(値オブジェクト化)やビルダーへの移行を検討すると、理解しやすさが劇的に上がります。


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

引数付きコンストラクタは「生成時に必要な情報を受け取り、検証し、一度だけ正しく代入する」ための要。final で不変を守り、契約違反は例外で早期に失敗させる。継承では親を先に初期化し、初期化順序を理解する。複雑な生成はファクトリやビルダーへ、値オブジェクトは record で簡潔に——この型を身につければ、生成から整合性が確かな設計が自然に書けます。

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