Java | オブジェクト指向:フィールド(メンバ変数)

Java Java
スポンサーリンク

フィールドの全体像

フィールド(メンバ変数)は、オブジェクトが「覚えておく状態」です。クラスの中に宣言し、各オブジェクトがそれぞれの値を持ちます。Java では「インスタンスフィールド(個体ごと)」と「static フィールド(クラス全体で共有)」の2種類があり、可視性(private など)や不変性(final)を適切に使うことで、壊れにくく読みやすい設計になります。


インスタンスフィールドと static フィールド

インスタンスフィールド(個体の状態)

各オブジェクトが独立の値を持ちます。ゲッター・変更メソッドで安全に操作します。

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");
        this.id = id;
        this.name = normalize(name);
    }
    public String id() { return id; }
    public String name() { return name; }
    public void rename(String newName) {
        var x = normalize(newName);
        if (x.isEmpty()) return;
        this.name = x;
    }
    private static String normalize(String s) { return s == null ? "" : s.trim(); }
}
Java

同じ User クラスから作ったオブジェクトでも、name はそれぞれ別の値を持ちます。

static フィールド(クラス全体の共有)

全オブジェクトで共有される値です。定数や設定、作成数など「共通の事実」に限定して使います。

public final class Counter {
    private int value;                 // 個体の値
    private static int created = 0;    // 共有の作成数

    public Counter() { created++; }
    public void inc() { value++; }
    public int value() { return value; }
    public static int createdCount() { return created; }
}
Java

static を乱用するとテストが難しくなるため、共有すべきものだけに絞るのが安全です。


可視性とカプセル化(重要ポイントの深掘り)

フィールドは原則 private にします。外から直接いじれると「整合性ルール」を破られます。代わりに、検証付きメソッドで正しい変更だけ許可します。

public final class BankAccount {
    private int balance;  // 外から直接触らせない

    public BankAccount(int initial) {
        if (initial < 0) throw new IllegalArgumentException("negative");
        this.balance = initial;
    }
    public int balance() { return balance; }
    public void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException("amount>0");
        balance += amount;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
}
Java

「直接代入禁止」と「検証付きの窓口」の二本柱で、オブジェクトは常に正しい状態を保てます。


不変性(final)と初期化の順序(重要ポイントの深掘り)

final フィールドは「コンストラクタ完了までに一度だけ代入」され、その後は変更不可になります。不変は並行性・テストで特に強力です。

public final class Token {
    private final String value;
    public Token(String v) { this.value = v; }  // 以後不変
    public String value() { return value; }
}
Java

new の内部では「メモリ確保→デフォルト初期化(数値0、参照null)→フィールド初期化式→親→子のコンストラクタ」という順序で進みます。初期化式とコンストラクタの役割を意識すると、未初期化や null の事故を避けられます。

public final class Counter {
    private int value = 10;  // フィールド初期化式
    public Counter() { value++; }      // 11 になる
}
Java

デフォルト値と null 安全

参照型フィールドは初期化しないと null です。null を前提にしない設計(早めの初期化、空オブジェクトや Optional を使う)にすると安全性が上がります。

public final class Profile {
    private String nickname = "";   // から文字で初期化(nullを避ける)
    public void setNickname(String s) { nickname = s == null ? "" : s.trim(); }
}
Java

配列やコレクションは、意図がなければ空で初期化しておくと扱いやすくなります。

private final java.util.List<String> tags = new java.util.ArrayList<>(); // 空から
Java

this と名前のシャドーイング

コンストラクタ引数やメソッド引数の名前がフィールド名と同じ場合、this を使って「フィールド側」を指します。これで曖昧さを防げます。

public final class Point {
    private final int x, y;
    public Point(int x, int y) {
        this.x = x;  // フィールドへ代入
        this.y = y;
    }
}
Java

ローカル変数でフィールド名を隠す(シャドーイング)と読みづらくなるため、基本は避けます。


派生値(計算プロパティ)と公開の仕方

「計算で得られる値」はフィールドに持たず、メソッドで返すと整合性が保てます。冗長なコピーを減らし、常に最新の値が得られます。

public final class Rectangle {
    private final int w, h;
    public Rectangle(int w, int h) { this.w = w; this.h = h; }
    public int width() { return w; }
    public int height() { return h; }
    public int area() { return w * h; }  // 面積は計算で返す(保持しない)
}
Java

特殊な修飾子(static final 定数、transient、volatile)

定数は public static final で定義し、意味のある名前で再利用します。

public final class Consts {
    private Consts() {}
    public static final int MAX_RETRY = 3;
    public static final java.nio.charset.Charset UTF8 = java.nio.charset.StandardCharsets.UTF_8;
}
Java

シリアライズ対象から外すなら transient、複数スレッドで「可視性」を担保するなら volatile を使います(ロックやアトミック型を選ぶ方が安全な場面が多いです)。

public final class Session implements java.io.Serializable {
    private transient String secret;        // 保存対象から除外
    private volatile boolean active;        // 変更の可視性を担保
}
Java

例題で身につける

例 1: 不変フィールド+検証付き変更

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

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

    public void rename(String newName) {
        var x = normalize(newName);
        if (!x.isEmpty()) this.name = x;
    }
    public void reprice(int newPrice) {
        if (newPrice >= 0) this.price = newPrice;
    }
    private static String normalize(String s) { return s == null ? "" : s.trim().replaceAll("\\s+", " "); }
}
Java

例 2: 共有設定と個体の状態を分離

public final class Mailer {
    public static String defaultFrom = "noreply@example.com";  // 共有(必要なら final に)
    private final String to;
    private final String subject;
    public Mailer(String to, String subject) { this.to = to; this.subject = subject; }
    public String from() { return defaultFrom; }
}
Java

共有値は static、送信先や件名など個体の状態はインスタンスフィールドで表します。


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

フィールドは「オブジェクトの状態」。個体ごとはインスタンスフィールド、全体共有は static フィールド。原則 private にして検証付きメソッドで操作し、不変にできるものは final にする。計算で得られる値はフィールドに持たずメソッドで返す。初期化の順序と null を理解して安全に立ち上げる——この型を守るだけで、状態管理が格段に安定します。

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