Java | オブジェクト指向:インスタンス変数のスコープ

Java Java
スポンサーリンク

全体像

インスタンス変数(フィールド)は「オブジェクトがずっと覚えている状態」で、クラスの中に宣言します。スコープは「どこからその変数が見えるか」を意味し、インスタンス変数は基本的に同じクラスの全メソッドから参照できます。ただしアクセス修飾子(private, package-private, protected, public)やネスト・継承の関係で見える範囲が変わります。ライフタイム(いつまで生きているか)は、オブジェクトが参照されている間ずっとで、メソッドの一時変数とは異なります。


スコープとライフタイムの違い

スコープの定義(参照可能範囲)

インスタンス変数はクラス本文の中で宣言され、そのクラスのインスタンスメソッドから参照できます。可視性が private なら同一クラス内のみ、protected や public なら他クラスからも条件付きで参照できます。スコープは「言語上どこから見えるか」の話で、オブジェクトの生存期間とは別です。

public final class User {
    private String name;      // このクラスの中だけで参照可(private)
    public int age;           // どこからでも参照可(public)

    public String name() { return name; }             // 参照できる
    public void rename(String newName) { this.name = newName; } // 更新できる
}
Java

ライフタイム(いつ生きているか)

インスタンス変数は「そのオブジェクトが生きている間」存在し続けます。メソッドのローカル変数が呼び出しの終了とともに消えるのに対し、フィールドはオブジェクトがガベージコレクションされるまで保持されます。これが「長期状態」と「短命な作業メモ」の違いです。

var u = new User();
u.rename("Taro");           // フィールドに保持され続ける
// u がどこからも参照されなくなれば、いつか GC により回収
Java

アクセス修飾子と可視性

private・package-private・protected・public の見え方

private は「同じクラス内だけ」。package-private(修飾子なし)は「同じパッケージ内」。protected は「同じパッケージ+継承先から参照可」。public は「どこからでも」。ドメインルールを守るため、原則 private にしてメソッド経由で安全に公開するのが基本です。

package com.example.domain;
public class Person {
    private String name;          // 同クラス内のみ
    int score;                    // 同パッケージ内(package-private)
    protected String nickname;    // 同パッケージ+サブクラス
    public int age;               // どこからでも

    public String name() { return name; } // 安全な窓口
}

// 別パッケージのサブクラス
package com.example.app;
public class Student extends com.example.domain.Person {
    void demo() {
        // name は private なので不可
        // score は別パッケージなので不可
        nickname = "S";  // protected なので参照・更新可(サブクラスから)
        age = 16;        // public は参照・更新可
    }
}
Java

メソッドからのアクセスと this

同じクラスのすべてのメソッドから参照できる

インスタンスメソッドは、そのインスタンスのフィールドに自由にアクセスできます。フィールドと引数やローカル変数の名前が重なるときは this を付けてフィールドの方を明示すると誤解がなくなります。

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

    public void rename(String name) {
        var x = name == null ? "" : name.trim();
        if (!x.isEmpty()) this.name = x; // フィールド更新を明示
    }
    public void reprice(int price) {
        if (price >= 0) this.price = price; // フィールド更新
    }
}
Java

static 文脈とインスタンス変数

static メソッドでは参照できない理由

static メソッドには「今のインスタンス」が存在しないため、直接フィールドへは触れられません。必要ならインスタンスを引数で受け取るか、インスタンスメソッドへ移して設計します。

public final class User {
    private String name;
    public String name() { return name; }

    public static void print(User u) {          // インスタンスを受け取る
        System.out.println(u.name());
    }
}
Java

内部クラス・継承でのスコープの細かい話

内部クラスから外側のインスタンス変数へアクセス

非 static の内部クラスは外側インスタンスに紐づくため、Outer.this を使えば外側のフィールドへアクセスできます。名前が重なる場合にも明確に指し示せます。

class Window {
    private String title = "Main";
    class Button {
        void click() {
            System.out.println(Window.this.title + " clicked"); // 外側のフィールド
        }
    }
}
Java

protected の継承可視性と制約

protected は「サブクラスから参照可」ですが、異なるパッケージで「別のインスタンスの protected フィールド」を触るのは制約があります。自分自身(this)の継承されたメンバーを扱う設計に寄せると混乱が減ります。


初期化順序とスコープの安全設計(重要ポイントの深掘り)

デフォルト値・初期化式・コンストラクタの流れ

new の内部は「メモリ確保 → 型ごとのデフォルト(数値0, boolean false, 参照 null)→ フィールド初期化式 → 親コンストラクタ → 子コンストラクタ」。この順序を前提に、軽い既定値は初期化式、検証と確定はコンストラクタに集約すると、安全で読みやすい初期化になります。

public final class Config {
    private String region = "ap-northeast-1"; // 軽い既定値
    private int timeoutMs = 3000;             // 軽い既定値
    public Config(String region, int timeoutMs) {
        if (timeoutMs <= 0) throw new IllegalArgumentException("timeout>0");
        if (region != null && !region.isBlank()) this.region = region.trim();
        this.timeoutMs = timeoutMs;
    }
}
Java

不変性(final)とカプセル化で整合性を守る

final フィールドはコンストラクタ完了までに一度だけ代入され、その後は変更不可です。原則 private にし、検証付きのメソッドでのみ状態を変えると、スコープが広くても整合性が崩れません。

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

例題で身につける

例 1: スコープと可視性の違いを体感

public final class Account {
    private int balance;        // このクラス内のみ
    public String owner;        // どこからでも

    public Account(String owner, int initial) {
        if (initial < 0) throw new IllegalArgumentException();
        this.owner = owner;
        this.balance = initial;
    }
    public int balance() { return balance; }
    public void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException();
        this.balance += amount;
    }
}
// 他クラスから:owner は見えるが balance は見えない(メソッド経由で操作)
Java

例 2: static からのアクセス設計

public final class Printer {
    public static void printBalance(Account acc) {
        System.out.println(acc.balance()); // インスタンス経由で参照
    }
}
Java

例 3: 内部クラスで外側のフィールドを使う

class Cart {
    private int total = 0;
    void add(int price) { total += price; }
    class Summary {
        String text() { return "total=" + Cart.this.total; }
    }
}
Java

よくあるつまずきと回避

インスタンス変数を public にして直接いじると、整合性が簡単に壊れます。原則 private にし、検証付きメソッドで操作してください。static メソッドからフィールドへ直接触ろうとして「インスタンスがない」問題に遭遇しがちです。インスタンスを受け取るか、インスタンスメソッドへ移す設計が正解です。フィールド名とローカル名の衝突(シャドーイング)は誤読の元なので、this を明示するか、ローカル名を変えて避けましょう。継承で protected を使う場合、可視性の範囲と「どのインスタンスを触ってよいか」の制約を理解して、自分自身の継承メンバーを扱う設計に寄せると安全です。


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

インスタンス変数のスコープは「同じクラスの中では広いが、外からはアクセス修飾子で制御する」が基本です。ライフタイムはオブジェクト存続期間と一致し、メソッドローカルとは別物。static 文脈ではインスタンスがないため直接触れず、必要ならインスタンスを渡す。内部クラスや継承下での可視性の細則を押さえ、初期化順序・不変性・カプセル化で整合性を守る――この型を身につけると、状態管理が安定し、クラス設計がぐっと強くなります。

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