Effective Javaを読むチャレンジ-項目22その1-

項目22 非staticのメンバークラスよりstaticのメンバークラスを選ぶ

メンバークラスというのはネストされたクラスの一種です。ネストされたクラスがネストの外側のクラスのメンバとして扱われるためにそう呼んでいるのだと思います。

この項目はJavaのネストされたクラスをパターン化してそれぞれについて利用シーンなどを解説しています。そのネストされたクラスのパターンは、staticのメンバークラス、非staticのメンバークラス、無名クラス、ローカルクラスです。

staticメンバークラス

staticメンバークラスは、例えばこんな感じです。

このネストされたstaticメンバークラスのフィールドやメソッドは、エンクロージングクラスからは以下のようにアクセスすることができます。

外側のクラスからprivateであるフィールドやメソッドにもアクセスできていることがお分かりかと思います。ただし、staticメンバークラスが別のパッケージや別のクラスに宣言されている場合、privateなフィールドやメソッドにはアクセスできません。これはstaticメンバークラス内のフィールドやメソッドがクラスに宣言されたstaticフィールドなどと同じアクセス可能性を持っているということです。本書の2段落目はこのことを言っています。

staticメンバークラスは、インスタンスの生成にエンクロージングクラス(外側のクラス)のインスタンスを必要としません。そしてそれはthis参照ではありません。実際は、Enclosing.StaticMemberというようにエンクロージングクラスのstaticメンバのようにアクセスしています。同じクラスにあるので、その外側のクラス名を省略してもコンパイルされるというだけです。

ところでこのstaticメンバークラスは前回の項目21で出てきたStrategyパターンの例である、java.lang.StringクラスのCASE_INSENSITIVE_ORDERというフィールドの具象戦略で使われています。Stringクラスのソースは以下です。

staticメンバークラスであるCaseInsensitiveComparatorクラスのインスタンスを、エンクロージングクラスであるStringクラスのインスタンスなく生成しているのが分かると思います。

staticメンバークラスの一般的な使用方法の1つは、その外部クラスと一緒に使用すると有用なヘルパークラスだと本書では述べています。本書ではその例として項目30のサンプルとして出てくるOperationというenum型を挙げています。おそらくこの「外部クラスと一緒に使用すると有用なヘルパークラス」というのは、例えば左記の例でいうCaluculator.Operation.MINUSといったように、自然な名前でネストされたヘルパークラスを使えるということでしょう。

非staticメンバークラス

非staticメンバークラスは、例えばこんな感じです。

一見、「static class」からstaticを取っただけの、staticメンバークラスとほとんど変わらないように見えますが、両者の大きな違いは非staticメンバークラスはエンクロージングインスタンスに暗黙的に関連付けられるというところにあります。

この非staticメンバークラスとstaticメンバークラスの内部から、エンクロージングクラスのフィールドにアクセスしてみると、両者の違いが分かります。

非staticメンバークラスからは、「Enclosing.this」という、本書の言葉で言うと修飾されたthis構文の形でエンクロージングクラスのstaticでないフィールドにアクセスすることができます。エンクロージングクラスのメソッドも同様です。

一方、staticメンバークラスからはエンクロージングクラスのstaticでないフィールドにアクセスすることはできません。「スコープ内で型 Enclosing のエンクロージング・インスタンスがアクセス不可能です」というコンパイルエラーが発生します。

エンクロージングクラスのインスタンスなしで、非staticメンバークラスのインスタンス生成は不可能

今度は、非staticメンバークラスとstaticメンバークラスを外部のクラスからインスタンス化してみます。そうすると、本書のこの内容が何を言っているか分かります。上記のサンプルクラスのアクセス修飾子をpublicに変えて別パッケージのクラスからインスタンスを生成しようとしてみます。

非staticメンバークラスのインスタンス生成がコンパイルエラーになりました。「アクセス可能な型 Enclosing のエンクロージング・インスタンスがありません。型 Enclosing のエンクロージング・インスタンスで割り振りを限定する必要があります (例えば x.new A() で、x は Enclosing のインスタンス)。」というコンパイルエラーが発生します。このエラーは下記のような形で回避できます。

このように、非staticメンバークラスのインスタンスを生成するには、関連付けられた(すなわちネストしている)エンクロージングクラスのインスタンスが必要です。項目22の1ページ目の最後の段落は、このことを言っています。見てお分かりの通り、非staticメンバークラスのインスタンスを生成するには、エンクロージングクラスのインスタンス生成のコストが余計に掛かります。

この非staticメンバークラスの使用方法の1つは、エンクロージングクラスのインスタンスと関係のないクラスのインスタンスとしてみなすことを可能にするアダプター(Adapter)とあります。GoFのAdapterパターンは別のクラスのインスタンスを自分のクラスのインスタンスのように提供するデザインパターンですが、これは非staticメンバークラスのインスタンスをエンクロージングクラスのインスタンスのように提供します。

本書の例の中のMapインタフェースを実装しているクラスを見てみます。java.util.HashMapクラスのkeySetメソッドです。

このコードのnew KeySet()で生成しているインスタンスは、同クラスに宣言されている非staticメンバークラスのjava.util.HashMap.KeySetクラスです。

最終的に、staticメンバークラスと非staticメンバークラスをどう使い分けるかというと、エンクロージングクラスのインスタンスへアクセスする必要がないメンバークラスを宣言するのであればstaticメンバークラスにする、ということです。

たった2ページ読むのにこの苦労。あと1ページですが、無名クラスとローカルクラスについては次回以降になります。

広告
  • LINEで送る