Java入門では割愛としていたローカルクラスと、ついでに関連の強い匿名クラスについて書いておきます。ネストしたクラスとかインナークラス(内部クラス)とかはそこそこしっかり書いたつもりなので、読んでいただけると嬉しいです。
Javaエンジニア養成読本 [現場で役立つ最新知識、満載!] (Software Design plus)
- 作者: きしだなおき,のざきひろふみ,吉田真也,菊田洋一,渡辺修司,伊賀敏樹
- 出版社/メーカー: 技術評論社
- 発売日: 2014/11/11
- メディア: 大型本
- この商品を含むブログ (6件) を見る
言語仕様とか読んでもいいと思いますし、この辺りも参考に見てみるといいと思います。
- ローカルクラスの意義 - ぐるぐる~ 2009/3/14
- Javaのクラス宣言5種+α - プログラマーの脳みそ 2012/3/8
- Javaのクラスの使用 - ひしだま's 技術メモページ 2010/1/9
みなさまにはいつもおせわになってます。
宣言とか呼び方とか
それぞれどのようなコードで宣言するかと、呼び方について確認しておきます。 もともと書こうと思ったきっかけは呼び名が揺れまくって混乱してる人を見かけたからなので。
匿名クラス
単純に作るとこんな感じになります。
Object obj = new Object() {};
これだけで匿名クラスのインスタンスができます。呼ばれ方は次のようにいろんなバリエーションある。
- 匿名クラス
- 無名クラス
- 匿名内部クラス
- 無名内部クラス
- 匿名インナークラス
- 無名インナークラス
正規表現だと (匿|無)名(内部|インナー)?クラス
ですかね?
どれでもだいたい同じものを指しているので、適当に読み替えてください。
ローカルクラス
これも単純に作るとこんな感じ。
class Hoge { { class Fuga { } } }
これで Fuga
がローカルクラスです。ブロックの中で宣言すればローカルクラス……と言おうと思ったけど、class
も構文上はブロック(波かっこに囲まれているのをブロックと言うならば)ぽいので困った。とりあえず、メソッドやイニシャライザ、コンストラクタとかで宣言すればローカルクラスです。呼び名はこんな感じ。
- ローカルクラス
- ローカル内部クラス
- ローカルインナークラス
内部つくかつかないかくらいですけれど、これもまー同じようなものです。適当に読み替えてください。
補足: 内部クラスと呼ぶか?
内部クラスとstaticなネストされたクラスが区別されることは意外と少ないのですが、意識して欲しいと思ってます。内部クラスは外側のクラスのインスタンス(エンクロージングインスタンス)への参照を持つものです。このことは、GC対象になるタイミングなどに影響があります。内部クラスのインスタンスがある限り、エンクロージングインスタンスへの参照があることになります。そのため、内部クラスと呼ばれるクラスのインスタンスは下手に公開してはいけないなど、取り扱いに注意が必要になります。
匿名クラスやローカルクラスがstaticでないメンバ(インスタンスメソッドなど、通常の用途です。)で宣言された場合、内部クラスとなる可能性があります。エンクロージングインスタンスのメンバにアクセスしなければコンパイル時にエンクロージングインスタンスの参照を持たないようにコンパイルされますが、一つでも使うと参照を持つ形になります。また、staticなメンバで宣言されたとしても、そのブロック内の変数を参照する実装がされているならば、その変数の指すオブジェクトへの参照を内部クラスは持つことになります。この場合もコンストラクタや引数で渡された以外の参照を持つことになりますので、注意が必要です。
例えば以下のようにすると、ローカルクラスFuga
にはfield
を使用するためにHoge
を、arg1
を使用するためにObject
を受けるコンストラクタができることになります。興味があればjavac/javapしてみてください。
class Hoge { Object field; void method(Object arg1, Object arg2) { class Fuga { void m1() { System.out.println(field); System.out.println(arg1); } } } }
定義する状況と定義内容により、コンストラクタの暗黙の引数経由で参照を暗黙的に持ち回すようになるかが変わります。構文的な差はなく、単に使用するか否かだけでうっかり参照を持つので、基本的に参照を持つもの内部クラスとして扱った方が安全かなと思っています。
で、なんと呼ぶ?
短い方が楽だから「匿名クラス」と「ローカルクラス」で。全部脳内でエイリアス貼っておけばいいんじゃないのー、とか思ってたりします。なのでこの記事ではこの呼び名で通してます。
匿名クラスとローカルクラスの違い
わかりやすい違いは、名前がつくかどうかです。名前がつけばインスタンスが作れます。つまり、同じクラスから作られた匿名クラスのインスタンスはないけれど、ローカルクラスのインスタンスは複数作れます。そのことが役に立ったことは今までありませんが。
次によく挙げられるのが、匿名クラスはコンストラクタを持てない点。イニシャライザで代替できるという話は、この記事書き始めてから知りました。なるほどと思いました。 確かにコンストラクタでしかできないことを代替することはできるけれど、少々無理矢理感ある。無理矢理やってるのだから正しいけれど。
もう一つの違いは拡張についてです。匿名クラスは1つの親クラスを明示的に拡張するか、1つのインタフェースを実装することにより暗黙でObject
クラスを拡張するかしかできません。一方でローカルクラスは、通常のクラスと同じように1個の親クラス(なにも書かなければObjectクラス)を継承できて、0個以上のインタフェースを実装できます。
一番大きな違いは3つ目じゃないかなぁと思ってたりします。でもここで挙げた3つのことが実務で役に立ったことは今までありません。というか、ローカルクラスを書いた記憶はないです。
補足: クラスの名前
class Hoge { { class Fuga {} new Object(){}; } }
これコンパイルしたら Hoge$1.class Hoge$1Fuga.class Hoge.class の3ファイルできます。
それぞれのインスタンスに対して instance.getClass().getName()
をしたらそのまま Hoge$1
とか Hoge$1Fuga
とかになります。
匿名クラスとは言え、一応名前はあることはある。で、一応コンパイルした後なら new Hoge$1()
とか new Hoge$1Fuga()
とかできたりする。一応。やらないけど。
意識してほしいこと
知識が十分でないならば、内部クラス系(staticでないネストしたクラス、ローカルクラス)の乱用は避けましょう。いちいちクラスファイル作るのが面倒だとかクラス作るのにエクセルの申請書がいるとか言ってないで、普通にクラス作ったほうが安全ですし、変に巧妙なコードよりもわかりやすくてまともなコードになると思います。
また、内部クラスを作ったならば、そのインスタンスは下手に公開しないように注意しましょう。
匿名クラスは使い所です。一部はLambldaで置き換えられるでしょうが、全てがそういうわけではないはずです。また、本当に匿名クラスで実装するのが正しいかは一考の余地があるはずです。匿名クラスでの実装を想定するAPIのライブラリやフレームワークは数ありますが、「サンプルコードでそうなっていたから」と言って、常にそうするのが正しいわけではありません。
まとめ
匿名クラスはともかく、ローカルクラスは本当に使いません。使い所もわかりません。無理に使わないでいいと思います。 ……有効に使っている例があったらこっそり教えてください!