Java | オブジェクト指向:getter / setter

Java Java
スポンサーリンク

全体像

getter / setter は「フィールド(内部状態)を外から安全に読み書きするための窓口」です。直接フィールドを公開せず、メソッド経由にすることで、読み取りは意図を伝え、書き込みは検証や前処理を挟めます。Java では命名規則が決まっており、フレームワーク(JavaBeans、ORM、シリアライザ等)が自動認識できるため、実務での互換性も高い設計になります。


基本形と命名規則

標準的な getter / setter

読み取りは getX(boolean は isX)、書き込みは setX。フィールドは private にして、外からはメソッド経由でアクセスします。

public final class User {
    private String name;                 // 直接公開しない

    public String getName() {            // 読み取り
        return name;
    }
    public void setName(String name) {   // 書き込み(検証・前処理を挟める)
        this.name = normalize(name);
    }

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

boolean の命名

boolean の読み取りは isX が慣例です。書き込みは setX のままで構いません。

public final class Feature {
    private boolean enabled;

    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
}
Java

重要ポイントの深掘り:検証とインバリアント(不変条件)

setter は「自由な代入口」ではなく「検証付きの操作」

オブジェクトの整合性を守るため、setter では必ず前提をチェックします。値の正規化や範囲チェック、関連フィールドの一貫性維持をここで行います。

public final class Product {
    private int price;

    public Product(int price) { setPrice(price); }      // 入口を統一

    public int getPrice() { return price; }

    public void setPrice(int price) {                   // 検証を必ず通す
        if (price < 0) throw new IllegalArgumentException("price>=0");
        this.price = price;
    }
}
Java

連動する状態は一つの操作で扱う

複数フィールドの整合性が絡む場合、「個別 setter の連発」よりも、意味ある一括操作にまとめる方が安全です。

public final class Period {
    private java.time.LocalDate start, end;

    public java.time.LocalDate getStart() { return start; }
    public java.time.LocalDate getEnd() { return end; }

    public void setRange(java.time.LocalDate start, java.time.LocalDate end) { // 一括で整合性チェック
        if (start == null || end == null || end.isBefore(start)) {
            throw new IllegalArgumentException("end >= start");
        }
        this.start = start;
        this.end = end;
    }
}
Java

ドメイン設計と getter / setter の使い分け

「何でも setter」より「意味のある操作」を優先

セッターを無制限に増やすと、オブジェクトのルールが簡単に破られます。値の変更はドメイン動詞で表現し、その中で検証・副作用を制御します。

public final class BankAccount {
    private int balance;

    public BankAccount(int initial) {
        if (initial < 0) throw new IllegalArgumentException();
        this.balance = initial;
    }
    public int getBalance() { return balance; }

    // ドメイン操作(検証付き)
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
    public void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException();
        balance += amount;
    }
}
Java

DTO とドメインオブジェクトで方針を分ける

  • データ受け渡し用(DTO)はシンプルな getter / setter で十分。バリデーションは受け側で。
  • ルールを持つドメインオブジェクトは「操作優先」、必要最小限の getter のみに絞る。
// DTO(外部入出力用)
public final class UserDto {
    private String id;
    private String name;
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}
Java

不変設計の代替案:setter をなくす選択肢

コンストラクタで確定+with メソッド(新インスタンスを返す)

不変に寄せると並行性・テストが強くなります。変更は新インスタンスを返す「with」スタイルに。

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

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

    public Money withAmount(int newAmount) {           // setter の代わり
        return new Money(newAmount, currency);
    }
}
Java

record の活用(値オブジェクトを簡潔に)

値オブジェクトは record で短く書け、getter 相当のアクセサのみ(不変)になります。検証が必要ならコンパクトコンストラクタを使います。

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

フレームワークとの相性(JavaBeans の前提)

JavaBeans 規約に沿うと便利

多くのフレームワークは「private フィールド+public getX/setX」を前提に自動マッピングします。命名と可視性を守ると、設定やシリアライズがスムーズです。

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

    public String getRegion() { return region; }
    public void setRegion(String region) { this.region = region; }

    public int getTimeoutMs() { return timeoutMs; }
    public void setTimeoutMs(int timeoutMs) { this.timeoutMs = timeoutMs; }
}
Java

必要なら可視性を絞る

JPA などで「引数なしコンストラクタ/プロパティアクセス」が必要でも、ドメイン側の操作は別に用意し、整合性を守る設計にします(protected コンストラクタ、検証付きメソッドなど)。


よくある落とし穴と回避

「何でも公開 setter」は危険

セッターを無差別に公開すると、オブジェクトの前提が壊れます。必要最小限に絞り、検証・前処理を必ず入れる。

セッターから例外方針が曖昧

失敗時は例外を投げるか、戻り値で知らせるかを統一。契約(受け入れる値、失敗の扱い)を明確にする。

連動するフィールドの一貫性崩壊

関連する値は一括操作にまとめ、個別 setter の並行使用で矛盾が生じないようにする。

性能最適化のためにフィールドを public にする

整合性が壊れる典型。getter は JIT でインラインされやすく、性能上の差は小さいことが多い。安全性を優先。


例題で身につける

例 1: 正規化を setter に集約して重複排除

public final class Person {
    private String name;
    public String getName() { return name; }
    public void setName(String name) {
        var v = name == null ? "" : name.trim().replaceAll("\\s+", " ");
        if (v.isEmpty()) throw new IllegalArgumentException("name required");
        this.name = v;
    }
}
Java

例 2: 整合性を守る一括操作と最小 getter

public final class Rectangle {
    private int w, h;
    public int getW() { return w; }
    public int getH() { return h; }
    public void setSize(int w, int h) {
        if (w <= 0 || h <= 0) throw new IllegalArgumentException("positive size");
        this.w = w; this.h = h;
    }
    public int area() { return w * h; } // 計算は読み取り専用のAPIで提供
}
Java

例 3: 不変オブジェクトで「with」スタイル

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

    public Config(String region, int timeoutMs) {
        if (region == null || region.isBlank() || timeoutMs <= 0) throw new IllegalArgumentException();
        this.region = region.trim();
        this.timeoutMs = timeoutMs;
    }
    public String getRegion() { return region; }
    public int getTimeoutMs() { return timeoutMs; }

    public Config withTimeout(int ms) { return new Config(region, ms); } // setter なし
}
Java

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

getter / setter は「外と中のあいだに検証と前処理の壁を作るための窓口」。フィールドは private にし、setter は必ず検証付き、連動する状態は一括操作へ。ドメインでは「意味のある操作」を優先し、DTO など受け渡し用はシンプルな getter / setter。不変が向く場面では setter を廃して with メソッドや record を活用する——この線引きができると、整合性が守られ、フレームワーク互換性と保守性の両立ができます。

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