コンパクトコンストラクターと検証
コンパクトコンストラクター内に検証ロジックを追加し、データの整合性を保証します。
「コンパクトコンストラクターと検証」は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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- レコード入門
- コンパクトコンストラクターと検証
- レコードのカスタムメソッド
- レコード、クラス、Lombok の比較