昨日知ったばかりではあるのですが、引き続き今日もJavaの匿名クラスと戯れてみようかと思います。
要求されるインターフェース
Androidのプログラムを書いていると、イベントコールバックなどで必ず要求されるのが「インターフェース」です。そして、インターフェースそのものを直接インスタンス化することはできませんから、何かしらのクラスに実装した上でインスタンス化して渡す必要があります。
一口にインターフェースと言っても、メソッド1つだけを持つRunnableから、10個以上のメソッドを実装する必要のあるMediaPlayerControlと、スケールもまちまちです。
匿名クラス以外で実装してみると
匿名クラスなど、クラスのネストを使わずに実装しようとすれば、大きく分けて2通りが考えられます。
別にクラスを立てる
いちばんの正攻法で、普通にクラスを立ててしまうという方法があります。単にコールバックを並べるだけでなく状態を持って引き回したいというのであればもちろん選択肢としてありです。ただ、Runnableのようなたった1つの関数を使うためだけにクラスを立てるのも大げさだ、という面があります。
呼ぶクラスに同居させる
インターフェースさえあればどんなクラスでも構わないので、メソッドのあるクラスにインターフェースを実装してしまってthis
を渡す、という選択肢もありです。この場合、実装したメソッド内からプライベートなメンバも使えて便利ではあるのですが、当然ながら同じインターフェースを複数実装するわけには行きませんし、すでにあるメソッドと返り値だけが違うメソッドのオーバーロードは不可能なので、実装不能な場合も出てきます。
匿名クラスにできること
匿名クラスの宣言は、その場でインターフェースもしくは基底クラスをnew
して、さらに中括弧で実装を続けることで行います。この中では、ふつうに同じクラスのメンバにアクセスできるだけでなく、外側のクラスのメンバにもシームレスにアクセスできてしまいます。さらには、メソッドのローカル変数や引数にも、その変数がfinal
であればアクセス可能です。そういうわけで、匿名クラスを使うことで、最初からクラスを立てる手間も省けて、そしてかなりの度合いでメソッドの一部であるかのように扱えるという、きわめて便利な性質のものとなっています。
匿名クラスにできないこと
「匿名」クラスという名前のとおり、このインスタンスのもととなったクラスに、表立っての名前は存在しません1。そういうわけで、
- 変数で受ける場合、基底クラス・インターフェースとして受けるしかないので、オーバーライドでなく独自に実装したメソッドが仮にあったとしても、外部からは呼び出せません。
- 同じ型のインスタンスを別途生成することはできません。
また、構文の都合上、1つの基底クラスかインターフェースかしか指定できません。
匿名クラスの中身
生成したクラスファイルを見れば一目瞭然ですが、「外側のクラス名$番号.class」というように、きちんとクラスが生成しています。そして、匿名クラス特有の機能についても、
- 外側のクラスへのアクセス→コンストラクタで渡していて、内部に隠しメンバ(
this$0
)として持っている - 外側のクラスのプライベートメンバへのアクセス→外側のクラスに「
access$番号
」というような隠しメソッドが用意してあって、それを経由してアクセスしている - メソッドの
final
ローカル変数へのアクセス→これまたコンストラクタでコピー
というような仕掛けで実現しています(逆アセンブル・逆コンパイルなどで状況は見えてきます)。
参考リンク
-
もちろん、後述のように内部的な名前は存在するので、リフレクションなどを駆使すればできなくはないかもしれませんが、そういう話はおいておきます。 ↩