Java | オブジェクト指向:static メソッドの制約

Java Java
スポンサーリンク

概要

static メソッドは「クラスに属する処理」で、インスタンス(オブジェクト)に依存しない計算や共通ユーティリティを表します。インスタンスがなくても呼べる一方で、インスタンス特有の情報には触れられないなど、いくつかの重要な制約があります。その制約を理解すると「どこを 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

this と super が使えない

static 文脈には「今のインスタンス」が存在しないため、this や super は使えません。レシーバに依存しない処理だけを書くのが前提です。


ポリモーフィズムの制約(重要ポイントの深掘り)

オーバーライドできない(動的ディスパッチに参加しない)

static メソッドは“隠蔽(hiding)”はされますが、オーバーライドはされません。どのメソッドが呼ばれるかは「参照型でコンパイル時に」決まります。多態性(ポリモーフィズム)を期待した設計には使えません。

class Base {
    static void hello() { System.out.println("base"); }
}
class Sub extends Base {
    static void hello() { System.out.println("sub"); } // 隠蔽(overrideではない)
}
Base b = new Sub();
b.hello(); // "base" が出力(参照型に基づく)
Sub.hello(); // "sub"
Java

「同じメソッド呼び出しが、型ごとに適切に切り替わる」設計が必要なら、static ではなくインスタンスメソッド+継承/インターフェースを使います。


ジェネリクスと static の制約

クラスの型パラメータは static から直接使えない

ジェネリッククラスの型パラメータ(T など)はインスタンスに紐づくため、static メソッドや static フィールドではそのまま使えません。必要なら「メソッド自体をジェネリックにする」か「具体型に落とす」設計にします。

public final class Box<T> {
    private final T value;
    public Box(T value) { this.value = value; }

    // NG: static で T を直接使えない
    // public static T invalid() { ... }

    // OK: メソッド自体に型パラメータを宣言
    public static <U> Box<U> of(U u) { return new Box<>(u); }
}
Java

継承・インターフェースでの扱い

クラス継承では「隠蔽」になる

前述の通り、サブクラスの static メソッドは同名の親メソッドを“隠す”だけで、動的には切り替わりません。呼び出し側がどの参照型を使っているかで決まります。

インターフェースの static メソッドは「インターフェース名から呼ぶ」

Java 8 以降、インターフェースにも static メソッドを定義できますが、実装クラスのインスタンスからは呼べません。インターフェース名を明示して呼びます。

interface Strings {
    static String normalize(String s) {
        return s == null ? "" : s.trim().replaceAll("\\s+", " ");
    }
}
// 呼び出し
String x = Strings.normalize("  a   b ");
Java

共有状態と副作用の注意(重要ポイントの深掘り)

static は「共有」とセットになる

static メソッドが「共有 mutable な static フィールド」を操作し始めると、並行性やテストが難しくなります。可能な限り「引数だけに依存し、戻り値で返す」純粋関数に寄せ、共有書き換えを避けましょう。どうしても共有が必要なら、同期やアトミック型で可視性と整合性を担保します。

public final class Stats {
    private static final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);
    public static int next() { return count.incrementAndGet(); } // 共有の安全な更新
}
Java

例題で身につける

例 1: 個体に依存しない純粋関数

個体の状態を使わない共通処理は static が適任です。

public final class MathUtil {
    private MathUtil() {}
    public static int clamp(int x, int min, int max) {
        return Math.max(min, Math.min(max, x));
    }
}
Java

例 2: インスタンス依存の処理は引数で受ける

インスタンスの情報が必要なら、対象を引数で受けて扱います。

public final class Product {
    private final int price;
    public Product(int price) { this.price = price; }
    public int price() { return price; }
}
public final class Pricing {
    public static int withTax(Product p, double rate) {
        return (int) Math.round(p.price() * (1 + rate));
    }
}
Java

例 3: ポリモーフィズムが必要ならインスタンスで

型ごとに振る舞いを切り替えたいなら、static ではなくインスタンスメソッド+多態性を使います。

public interface Shape { double area(); }
public final class Circle implements Shape {
    private final double r;
    public Circle(double r) { this.r = r; }
    public double area() { return Math.PI * r * r; }
}
public final class Rect implements Shape {
    private final double w, h;
    public Rect(double w, double h) { this.w = w; this.h = h; }
    public double area() { return w * h; }
}
Java

よくある落とし穴と回避

static メソッドからインスタンスフィールドへ直接触ろうとして失敗するケースが多いです。インスタンスを引数で渡す設計に切り替えるか、責務をインスタンスメソッドへ移してください。ポリモーフィズムを期待して static メソッドをサブクラスで定義しても、動的ディスパッチは働きません。型ごとに切り替えたいなら、非 static のオーバーライドを用います。ジェネリッククラスの型パラメータを static から使えない点にも注意し、必要ならメソッドに独自の型パラメータを宣言します。共有 mutable な static 状態は並行性バグの温床なので、極力不変(final)にし、更新が必要なら同期やアトミックを使います。


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

static は「クラスに属する非インスタンス依存の処理」。インスタンスメンバーに直接触れない、this/super が使えない、ポリモーフィズムに参加しない、ジェネリクスの型パラメータを直接使えない——この制約が本質です。共通計算は static に、個体の状態に紐づく振る舞いはインスタンスに。共有 mutable は避け、必要なら安全機構を用いる。ここを外さなければ、static とインスタンスの役割分担が自然に決まり、コードは読みやすく拡張に強くなります。

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