0Pricing
Java Academy · レッスン

コンパクトコンストラクターと検証

コンパクトコンストラクター内に検証ロジックを追加し、データの整合性を保証します。

「コンパクトコンストラクターと検証」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

コンパクトコンストラクタ

レコードのコンパクトコンストラクタは、コンポーネントが代入される前に実行されます。コンポーネントへの代入を繰り返さずに、データの検証や正規化を行えます。

標準コンストラクタとコンパクトコンストラクタ

標準の正規コンストラクタでは、コンポーネントへの代入を明示的に記述します。コンパクトコンストラクタではパラメータリストと代入を省略でき、本文の実行後に自動的に処理されます。

// Standard canonical constructor
record Range(int min, int max) {
    Range(int min, int max) {
        if (min > max) throw new IllegalArgumentException("min > max");
        this.min = min;  // explicit assignment
        this.max = max;
    }
}

// Compact constructor (same behavior, less code)
record Range2(int min, int max) {
    Range2 {  // no parameter list, no assignments
        if (min > max) throw new IllegalArgumentException("min > max");
        // components are assigned automatically after this block
    }
}

コンパクトコンストラクタでの検証

コンパクトコンストラクタは、レコードデータを検証する標準的な方法です。不変条件を守るため、早い段階で例外をスローしてください。

record Email(String address) {
    Email {
        if (address == null || address.isBlank())
            throw new IllegalArgumentException("Email cannot be blank");
        if (!address.contains("@"))
            throw new IllegalArgumentException("Invalid email: " + address);
        address = address.toLowerCase().strip(); // normalize (Java 16+)
    }
}

Email e = new Email("  Alice@Example.com  ");
System.out.println(e.address()); // alice@example.com

try { new Email("not-an-email"); }
catch (IllegalArgumentException ex) { System.out.println(ex.getMessage()); }

コンパクトコンストラクタでの正規化

コンパクトコンストラクタ内では、コンポーネント変数に代入される前に、その値を変更できます。これにより、構築時にデータを正規化できます。

record PersonName(String firstName, String lastName) {
    PersonName {
        firstName = capitalize(firstName);
        lastName  = capitalize(lastName);
    }

    private static String capitalize(String s) {
        if (s == null || s.isEmpty()) return s;
        return Character.toUpperCase(s.charAt(0)) +
               s.substring(1).toLowerCase();
    }

    public String fullName() { return firstName + " " + lastName; }
}

PersonName name = new PersonName("jOHN", "DOE");
System.out.println(name.fullName()); // John Doe

防御的コピー

配列やコレクションなどの可変コンポーネントについては、コンパクトコンストラクタで防御的コピーを行い、不変性を維持してください。

import java.util.*;

record Snapshot(List<String> items) {
    Snapshot {
        items = List.copyOf(items); // defensive copy — unmodifiable
    }
}

List<String> mutable = new ArrayList<>(List.of("a", "b", "c"));
Snapshot snap = new Snapshot(mutable);
mutable.add("d"); // doesn't affect snapshot
System.out.println(snap.items()); // [a, b, c]

try {
    snap.items().add("e"); // UnsupportedOperationException
} catch (UnsupportedOperationException e) {
    System.out.println("Snapshot is truly immutable!");
}

境界値を持つRangeレコード

min <= maxを保証し、便利なユーティリティメソッドを提供する実用的なRangeレコードです。

record Range(int min, int max) {
    Range {
        if (min > max) throw new IllegalArgumentException(
            "min (" + min + ") must be <= max (" + max + ")");
    }

    public boolean contains(int value) { return value >= min && value <= max; }
    public int size()  { return max - min; }
    public int clamp(int value) { return Math.max(min, Math.min(max, value)); }
}

Range valid = new Range(1, 10);
System.out.println(valid.contains(5));  // true
System.out.println(valid.clamp(15));    // 10
System.out.println(valid.size());       // 9

コンパクトコンストラクタの処理を分割する

複雑な検証では、ヘルパーメソッドに処理を切り出し、コンパクトコンストラクタから呼び出します。

record CreditCard(String number, String cvv, int expiryMonth, int expiryYear) {
    CreditCard {
        validateNumber(number);
        validateCvv(cvv);
        validateExpiry(expiryMonth, expiryYear);
        number = number.replaceAll("[^0-9]", ""); // strip spaces/dashes
    }

    private static void validateNumber(String n) {
        String digits = n.replaceAll("[^0-9]", "");
        if (digits.length() < 13 || digits.length() > 19)
            throw new IllegalArgumentException("Invalid card number length");
    }
    private static void validateCvv(String cvv) {
        if (!cvv.matches("[0-9]{3,4}"))
            throw new IllegalArgumentException("Invalid CVV");
    }
    private static void validateExpiry(int m, int y) {
        if (m < 1 || m > 12) throw new IllegalArgumentException("Invalid month");
        if (y < 2024)         throw new IllegalArgumentException("Card expired");
    }
}

コンパクトコンストラクタの複数のパターン

コンパクトコンストラクタで使用される一般的な検証パターンです。

record Percentage(double value) {
    Percentage {
        if (value < 0 || value > 100)
            throw new IllegalArgumentException(
                "Percentage must be 0-100, got: " + value);
        value = Math.round(value * 100.0) / 100.0; // round to 2 dp
    }

    public double asFraction() { return value / 100.0; }
}

Percentage tax = new Percentage(8.756);
System.out.println(tax.value());       // 8.76
System.out.println(tax.asFraction()); // 0.0876

非正規コンストラクタ

レコードには追加のコンストラクタを定義できますが、this(...)を使って正規コンストラクタに委譲する必要があります。

record Point(double x, double y) {
    // Non-canonical constructor: origin point
    Point() { this(0.0, 0.0); }

    // Non-canonical: polar coordinates
    static Point fromPolar(double r, double theta) {
        return new Point(r * Math.cos(theta), r * Math.sin(theta));
    }

    public double distance(Point other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        return Math.sqrt(dx*dx + dy*dy);
    }
}

Point origin = new Point();
Point p = Point.fromPolar(5, Math.PI/4);
System.out.printf("Distance: %.2f%n", origin.distance(p)); // 5.00

不変コレクションを持つレコード

ユーザー設定の不変スナップショットを保持し、構築時に検証と正規化を行うレコードです。

import java.util.*;

record UserPreferences(String theme, Set<String> enabledFeatures, int fontSize) {
    private static final Set<String> VALID_THEMES = Set.of("light", "dark", "system");
    private static final Set<String> VALID_FEATURES = Set.of("ai", "beta", "analytics");

    UserPreferences {
        if (!VALID_THEMES.contains(theme))
            throw new IllegalArgumentException("Unknown theme: " + theme);
        if (!VALID_FEATURES.containsAll(enabledFeatures))
            throw new IllegalArgumentException("Unknown feature in: " + enabledFeatures);
        if (fontSize < 10 || fontSize > 24)
            throw new IllegalArgumentException("fontSize must be 10-24");
        enabledFeatures = Set.copyOf(enabledFeatures); // defensive copy
    }
}

コンパクトコンストラクタの制限

コンパクトコンストラクタでは、次のことはできません:

  • コンポーネントを明示的に代入できません(ブロックの後で自動的に代入されます)
  • this()やsuper()を呼び出せません
  • 例外をスローすると、すべてのコンポーネントへの代入が行われません
record Safe(int value) {
    Safe {
        // CAN: validate and modify component variables
        if (value < 0) value = 0; // normalized to 0 if negative
        // value = this.value; // NOT NEEDED — assignment happens after block
    }
}

System.out.println(new Safe(-5).value());  // 0 (normalized)
System.out.println(new Safe(10).value());  // 10

クイックチェック

コンパクトコンストラクタ内でコンポーネント変数を変更すると、その変更はどうなりますか。

復習:コンパクトコンストラクタと検証

重要なポイント:

  • コンパクトコンストラクタはコンポーネントへの代入前に実行されるため、明示的な代入は必要ありません
  • コンポーネントの検証、正規化、防御的コピーに使用します
  • コンポーネント変数を変更して値を正規化します(小文字化、トリミング、コピーなど)
  • 不正なデータにはIllegalArgumentExceptionをスローして不変条件を守ります
  • 可変コレクションのコンポーネントにはList.copyOf / Set.copyOfを使用します
  • 非正規コンストラクタはthis(...)で正規コンストラクタに委譲する必要があります

よくある質問

「コンパクトコンストラクターと検証」レッスンは無料ですか?

はい。「コンパクトコンストラクターと検証」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「コンパクトコンストラクターと検証」で何を学びますか?

コンパクトコンストラクター内に検証ロジックを追加し、データの整合性を保証します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「コンパクトコンストラクターと検証」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. レコード入門
  2. コンパクトコンストラクターと検証
  3. レコードのカスタムメソッド
  4. レコード、クラス、Lombok の比較
← Java Academyに戻る