2009.07.10
AndroidでリフレクションとJavaCCを試す(前編)
Androidを使うと、Javaによる携帯電話プログラミングが数段パワフルになった感じがします。
Androidのドキュメントを読んでいると、どうやらリフレクションも使えるご様子。
リフレクションとは反響、反映のことです。転じて、Javaにおいてはプログラムが自分自身(プログラム要素)へアクセスするための手段を意味しています。名前だけではちょっとイメージしづらいですね。
簡単に言うと、Javaクラスのフィールドやメソッドの情報を取得するためのAPIです。
・・こんなクラスのこんなメソッドを呼び出したい・・・
・・が、そのメソッドやクラスの名前がプログラムを書いている時点では未知である
(たとえば、開発者ではなく、利用者が指定する)
そんなときに使います。
テストや分散処理などのフレームワークづくりをしたいときや、APIを提供したいときに便利です。
今回は以前、JavaCCを使って作成した電卓を拡張し、リフレクションでjava.lang.Mathに含まれるメソッドを呼び出すことが可能な、高機能電卓にしてみましょう。
さらに、Android上で電卓を実行できるようにします。
果たしてAndroid上でのリフレクションは簡単に使うことができるのか?JavaCCプログラムをAndroido上で動かすことはできるのか?
試したいと思います。
リフレクションなぞ知っとるわい、というかたは後半を、JavaCCによる構文解析器について知りたいかたは土日でつくるコンパイラをご参照ください。
前半ではAndroidでもJavaでも使える(はずの)リフレクションの利用手順を簡単に追っていくことにしましょう。
まずは
- 型にアクセスする。
そして、リフレクションを使って・・
- オブジェクトを生成する。
- メソッドを呼び出す。
↑ここまでをやってみましょう。
型情報(Classオブジェクト)へのアクセス方法はいろいろあります。クラス名がわかっていれば、
<クラス名>.class
でアクセスできます(ex. String.class)。
当該クラスのインスタンスを得ることができるなら、getClassメソッドを使います。
Object obj;
...
Class clazz = obj.getClass();
最後の手段はクラスローダを使って型情報を取得する方法です。
String className;//クラス完全に限定する名前
...
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class clazz = classLoader.getClass(className);
この方法であれば、プログラム外部から取得した名前を使って型情報を得ることができますね。
クラスローダにはいろいろな種類があって、自分で定義することもできます。
注意してほしいのは、
Java仮想マシンは、異なるクラスローダから呼び出したクラス同士を、たとえ名前が同じであっても、加えて内容がまったく同じであっても、さらには仮に同じ.classファイルから生成されたものであっても、異なるクラスとして扱う
ことです。
型情報を得ることができたので、次はオブジェクトを生成してみましょう。
その前に下準備。
import java.lang.reflect.*;
として必要なライブラリをインポートします。
オブジェクトの生成過程はどこに定義されているか?
コンストラクタですね。
リフレクションを使う場合でも、オブジェクトを生成するにはコンストラクタにアクセスします。
リフレクションでオブジェクトを生成する例を示します(コードの実用的価値は皆無)。
import java.awt.*;
...
Class[] paramClasses = new Class[]{String.class};
Constructor constructor = Frame.class.getConstructor(paramClasses);
Object[] parameters = new Object[]{”S-Cubism”};
Frame frame = (Frame)constructor.newInstance(parameters);
例1-1
リフレクションを使わないとこうなります。
Frame frame = new Frame("S-Cubism");
例1-2
ではコンストラクタはオーバーロードされますので、コンストラクタの特定には、引数リストの型を格納した配列を作る必要があります。
Class[] paramClasses = new Class[]{String.class};
引数型のリストが既知であれば、<クラス名>.classで、
未知であれば、引数となるオブジェクトから、getClassメソッドで取得しましょう。
Constructor constructor = Frame.class.getConstructor(paramClasses);
オブジェクトを生成します。必要な引数リストをnewInstanceメソッドに渡してやります。
Object[] parameters = new Object[]{”S-Cubism”};
Frame frame = (Frame)constructor.newInstance(parameters);
例1-1を少し実用的にするために、
Frameクラスの代わりにユーザが定義したFrameのサブクラスを使えるようにします。
import java.awt.*;
...
String className = System.getProperty("myclass");
Class classOfFrame = ClassLoader.getSystemClassLoader().getClass(className);
Class[] paramClasses = new Class[]{String.class};
Constructor constructor = classOfFrame.getConstructor(paramClasses);
Object[] parameters = new Object[]{”S-Cubism”};
Frame frame = (Frame)constructor.newInstance(parameters);
例1-3
システムプロパティmyclassに定義されたクラスを動的にロードし、オブジェクトを生成します。
最後にリフレクションを使って、メソッドを呼び出してみます。
リフレクションによってメソッドを呼び出す例を以下に示します。
Class[] paramClasses = new Class[]{Boolean.TYPE};
Method method = clazz.getMethod(”setVisible”, paramClasses);
Object[] parameters=new Object[]{new Boolean(true)};//引数リスト
method.invoke(frame, parameters);
例2-1
このコードは以下のコードと同義です。
frame.setVisible(true);
例2-2
メソッドはコンストラクタと同様にオーバーロードされますので、メソッド名だけでは一意に特定することができません。
ここでもコンストラクタと同様に引数型のリストを作ります。
Class[] paramClasses = new Class[]{Boolean.TYPE};
これでメソッドを特定できます。
メソッドの取得にはgetMethodメソッドを用います。
Method method = clazz.getMethod("setVisible", paramClasses);
これで、メソッドが特定されました。
Methodオブジェクトのinvokeメソッドを使い、目的のメソッドを実行します。
Object[] parameters=new Object[]{new Boolean(true)};//引数リスト
…
method.invoke(frame, parameters);
後半では、リフレクションによるメソッドアクセスを使って、電卓プログラムからjava.lang.Mathの数学関数を呼び出せるようにします。
Java標準APIにはリフレクションによるフィールドや配列へのアクセス方法も提供されていますので、APIドキュメントを参照されるとよいでしょう。
Trackback URL
Comment & Trackback
Comment feed
Comment