[Java] 内部クラスの使いどころ

2018年11月19日月曜日

Java

内部クラスのフィールドの可視性

Javaの内部クラスのフィールドはprivateにしてもエンクロージングクラスからは可視になっている。
class InnerClass {
    private static class StaticClass {
        private int var = 5;
    }

    public static void main(String ... args) {
        class LocalClass {
            private int var = 3;
        }

        LocalClass local = new LocalClass();
        int var = local.var;
        System.out.println(var);

        StaticClass slocal = new StaticClass();
        var = slocal.var;
        System.out.println(var);
    }
}
実行結果
3
5

一見、privateの意義に反しているように見えるけど、あえて意図的にこのような言語設計になっているように思う。

Javaのスコープは
  • 全体
  • 派生クラス
  • パッケージ内
  • クラス内部
の4つであり、クラス内部にスコープ規則が再帰的に適用されるわけではないという解釈もできなくはないけど、どうなんだろう。

ブリッジメソッド

本来アクセスできないprivateフィールドにアクセスするためにJavaのコンパイラはエンクロージングクラスとprivateフィールドを結ぶブリッジメソッドを生成している。

javapでブリッジメソッドが生成されている様子を見てみよう。
$ javap -l InnerClass\$1LocalClass
Compiled from "InnerClass.java"
class InnerClass$1LocalClass {
  InnerClass$1LocalClass();
    LineNumberTable:
      line 9: 0
      line 10: 4

  static int access$000(InnerClass$1LocalClass);
    LineNumberTable:
      line 9: 0
}

イメージ的にはこんな感じのメソッドが自動生成されて、このアクセスメソッドを通してprivateフィールドにアクセスしている。
class LocalClass {
    private int var = 3;

    // これが自動生成される。
    static int access000(LocalClass obj) {
        return obj.var;
    }
}

getのみしている場合、get/setしている場合で必要なアクセサのみが作られる。

内部クラスはエンクロージングクラスの一部

privateにしてもエンクロージングクラスから可視になっている理由として、内部クラスはエンクロージングクラスの所有であり、密接な関係があると考えれば、それほどおかしくはない。
そう考えると、(無名でない)内部クラスを使う場面としては以下のようなことを想定しているのかな。
  • クラス外で本来の用途以外で使われないことを保証したい。
  • 同じような構造が繰り返し現れる。
  • メソッドは持たせない。
  • 簡潔な記法にして、フィールドにアクセス修飾子をつけない。
だから、Javaにはタプルがないけど、タプルが使いたくなるような場面では内部クラスを使うのがいい選択なのかな。