読者です 読者をやめる 読者になる 読者になる

【Effective Java】項目22:非 static のメンバークラスより static のメンバークラスを選ぶ

Effective Java Java

クラスの中に定義されたクラスを『ネストしたクラス』と呼ぶ。

ネストしたクラスは4種類に分類できるが、非 static メンバークラスよりも static メンバークラスを利用するべき。

ネストしたクラス

ネストしたクラスには以下の4種類があり、static のメンバークラス以外は内部クラス(inner class)と呼ばれている。

  • static のメンバークラス
  • 非 static のメンバークラス
  • 無名クラス
  • ローカルクラス

static のメンバークラスと非 static のメンバークラス

static のメンバークラスは最も単純なネストしたクラス。

通常のクラスとの違いはエンクロージングクラスのメンバーすべてにアクセスできる点。 static のメンバークラス自体は、通常のアクセス制御に従う(private ならばエンクロージングクラスのみアクセス可能)。

非 static のメンバークラスは、static メンバークラスと比較して static 修飾子がないだけだが、機能としてはかなり異なっている。

非 static のメンバークラスは、エンクロージングインスタンスと呼ばれるそれを含むクラスのインスタンスと暗黙的に関連付けられている。 すなわち、エンクロージングインスタンスなしでは非 static のメンバークラスは存在できない。

// エンクロージングクラス
class Enclosing {
    private int field;

    Enclosing(int field) {
        this.field = field;
    }

    // メッセージが送られた Enclosing インスタンスにひもづく NonStatic インスタンスを生成
    NonStaticClass createNonStaticInstance(int field) {
        return new NonStaticClass(field);
    }

    // 非 static クラス
    class NonStaticClass {
        private int field;

        NonStaticClass(int field) {
            this.field = field;
        }

        @Override
        public String toString() {
            StringBuilder s = new StringBuilder(64);
            s.append("Enclosing.field: ");
            // エンクロージングクラスのインスタンスにアクセスする
            s.append(Enclosing.this.field);
            s.append(", NonStatic.field: ");
            s.append(this.field);
            return s.toString();
        }
    }
}

class Main {
    public static void main(String[] args) {
       // エンクロージングインスタンスを明示的にした紐付け
        Enclosing enclosing1 = new Enclosing(1);
        Enclosing.NonStaticClass nonStatic1 = enclosing1.new NonStaticClass(2);

        // createNonStaticInstance を使った暗黙的な紐付け
        Enclosing enclosing2 = new Enclosing(999);
        Enclosing.NonStaticClass nonStatic2 = enclosing2.createNonStaticInstance(1000);

        System.out.println(nonStatic1);
        System.out.println(nonStatic2);
    }
}

// 出力
// => Enclosing.field: 1, NonStatic.field: 2
// => Enclosing.field: 999, NonStatic.field: 1000

エンクロージングインスタンスへのアクセスが必要なければ、static クラスを利用するべきである。

static 修飾子を省略すると、非 static メンバークラスのインスタンスはエンクロージングインスタンスへの参照を持ち、エンクロージングインスタンスが適切に GC されない可能性がある。

無名クラス

無名クラスは名前をもたず、宣言されると同時にインスタンス化されるクラス。

無名クラスはインスタンスメソッドの文脈で書かれた場合、エンクロージングインスタンスを持ち、そうでない場合は持てない。 ただし、static メソッド内で書かれていた場合でも static のメンバーは持つことができない。

class Main {
    public static void main(String[] args) {
        final String var1 = "Variable1";
        new Object() {
            // static のメンバーは持てない
            static int STATIC = 1; // => コンパイルエラー
            public String output() {
                // エンクロージングインスタンスの var1 にアクセス可能
                return var1;
            }
        };
}

無名クラスには以下の制限がある。

  • 宣言された箇所以外でその無名クラスをインスタンス化できない
  • 名前がないので instanceof 検査できない
  • 構文上、複数のインタフェースの実装や、クラスを拡張しつつインタフェースの実装ができない
  • スーパークラスから継承したメソッド以外呼び出せない(名前がついてないため)

無名クラスの代表的な利用方法は、

  • 関数オブジェクト(項目21)の生成
  • プロセスオブジェクト(Runnable など)
  • static ファクトリーメソッド内での利用

である。

ローカルクラス

ローカルクラスはローカル変数を宣言できる場所で宣言でき、ローカル変数と同じスコープ規則に従うクラス。 スコープにひもづくため、エンクロージングクラスのメンバとしては扱えない。

メンバークラスと同様にインスタンスメソッド内で生成された場合、エンクロージングインスタンスを持つ。 ローカルクラスは一般的にあまり利用されず、またあまり利用しないべきである。

感想

明示的なエンクロージングクラスの紐付けって初めて見た。

いろいろと種類があって混乱しやすいので仕様へのリンクを張っておく(Java 7)

8.1.3. Inner Classes and Enclosing Instances

これでめでたく第4章終了。年内に区切りがついてよかった。

第5章はジェネリクス。理解せずに感覚で使ってることが多いところなで、ちゃんと理解できるようになるのは楽しみだー。