24.staticでないメンバクラスより、staticメンバクラスを選択すべし
ネストしたクラスはエンクロージングクラスを補助する目的のために存在すべきである。
もしネストしたクラスをそれ以外の文脈で使用したいなら、トップレベルクラスとして定義するべき。
ネストしたクラスの種類
ネストしたクラスには4種類あって、
- staticメンバクラス
- 非staticなメンバクラス
- 匿名クラス
- ローカルクラス
がある。staticメンバクラス以外は、内部クラスと呼ばれるものである。
本章ではどの場面でどのような理由からどのネストしたクラスを使用すべきか述べる。
staticメンバクラス
staticメンバクラスはエンクロージングクラスのstaticなメンバの一つで、そのほかのstaticなメンバと同じアクセシビリティのルールに従う。
よくあるstaticメンバクラスの使い方の1つは、アウタークラスと伏せて使用した場合にのみ有効なpublic なヘルパークラスとしてである。
例として、計算機のオペレーションを表すenumを考える(Item34)。
オペレーションenumはCalculatorクラスのpublic staticなメンバークラスであるべき。
Calcuratorの利用者は、オペレーションを Calculator.Operation.PLUSや Calculator.Operation.PLUS のように参照できる。
非staticメンバクラス
非staticなメンバクラスのインスタンスは、暗黙的にエンクロージングインスタンスへの参照を持つ。
非staticなメンバクラスの中のインスタンスメソッドにおいて、エンクロージングインスタンスのメソッドを呼ぶことができ、qualified thisを使用してエンクロージングインスタンスへの参照を得ることができる。
(qualified thisはエンクロージングクラス.thisのことを指している)
エンクロージングインスタンス抜きで非staticなメンバクラスのインスタンスを生成することは不可能、つまり、独立にさせたいのならstaticメンバクラスを選択する必要がある。
非staticメンバクラスのインスタンスと、エンクロージングインスタンスの参照が形成されるのは、メンバクラスのインスタンスが生成されたときであり、そのあとに変更されることはない。
通常、エンクロージングクラスのインスタンスメソッドから、非staticなメンバクラスのコンストラクタが呼ばれることによって、参照が形成される。
非staticメンバクラスのよくある使い方は、Adapterである。
Adapterを用いると、エンクロージングクラスのインスタンスが別のクラスのインスタンスであるかのように見せることができる。
具体的には、MapのkeySetやvaluesメソッドで返されるcollection的な見た目のものは、非staticメンバクラスを使用している。
ListのIteratorなんかもそう。
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
...
}
もし、メンバクラスがエンクロージングインスタンスにアクセスする必要がないのであれば、常にstaticとして宣言しておくべきである。
なぜなら、この間の参照を生成するのに時間とメモリが食われるということと、この参照がなければガベージコレクションの対象となっているかもしれないからである(Item7)
private static なメンバークラスは、エンクロージングクラスのコンポーネントの一部として表される。
例えば、Mapインスタンスを考えてみると、多くのMapの実装では、Mapのなかのkey-valueのペアとしてEntryオブジェクトを保有する。
Entryのメソッド(getKey、getValue、setValue)はmapのメソッドにアクセスする必要がないので、entryを非staticなクラスにするのは無駄である。
非staticにしても動くが、mapへの余計な参照が増えるだけでメモリの無駄使いである。
ネストクラスについて、外部公開するか迷っている場合も、staticにするか非staticにするかの選択は重要である。
ネストクラスを外部公開した場合、非staticからstaticに変えることはできない。(後方互換性を犯すことになる)
匿名クラス
匿名クラスは他のクラスに属するというよりは、使用される時に宣言、インスタンス化が同時に行われるといえる。
匿名クラスは非staticな文脈であれば、エンクロージングクラスのインスタンスを保有することができる。
しかし、staticな文脈で匿名クラスが書かれていたとしても、constant variable(finalが付与されたprimitiveかStringの変数)でない限りはstaticメンバをもつことはできない。
匿名クラスの制限
- 匿名クラスには以下のような制限がある。
- 宣言された箇所以外でインスタンス化することはできない
- instanceofでテストできないなど、クラスの名前が必要となる処理はできない
- 複数インターフェースを実装したり、クラスを継承してインターフェースを実装したりすることができない
- 匿名クラスの利用者は、匿名クラスのスーパークラスのから継承したものしか呼び出せない
lambdaが導入されるまでは、小さな関数オブジェクトやプロセスオブジェクト(?)を生成する手段として匿名クラスが用いられてきたが、現在はlambdaのほうが好まれる。
その他、匿名クラスのおもな使用例としては、staticなファクトリメソッドの実装で用いられる。(Item20)
ローカル内部クラス
ローカル内部クラスは、ローカル変数と同様のかたちで宣言され、スコープも同じかんじ。
可読性のために短く保つべき。
まとめ
ネストクラスは4つあり、それぞれ使い道が違う。
メソッドの外側でvisibleである必要があったり、メソッドの内部に置くには長すぎる時にはメンバクラスを用意する。
メンバクラスのそれぞれのインスタンスがエンクロージングクラスのインスタンスにアクセスする必要があるのならば、非staticにすべき。それ以外はstaticにすべき。
メソッド内のクラスに関して、1か所のみで使い、動きを規定してくれるような既存のクラスがある場合は匿名クラスを使う。それ以外はローカル内部クラスを使う。