reflectionには、大きくいって三つの働きがあります。第一は、あるクラス(正確に いうと、Classクラスのオブジェクトです)が、どのようなField、Method, Constructor から構成されているかをレポートする機能です。第二は、あるクラスのオブジェクトの Fieldsの値を読み/書きする機能です。第三は、あるクラスのオブジェクトのMethodや Constructorに、適当な引数を与えて呼び出す機能です。
以下では、簡単なRectangleクラス(リスト1)を作って、それをreflectionの対象に したいと思います。
---< リスト 1 >---------------------------------------------------------------- public class Rectangle { int width,height; public Rectangle(int width, int height){ this.width = width ; this.height = height ; } public Rectangle(){ this(1,1); } public int area() { return width * height ; } public void setWidth( int width ){ this.width = width ; } public int getWidth(){ return width ; } public void setHeight( int height ){ this.height = height ;} public int getHeight(){ return height ; } public void printArea(){ System.out.println("Width : " + getWidth() ); System.out.println("Height: " + getHeight()); System.out.println("Area : " + area()); } public static void main(String argv[]) { Rect c = new Rect(3,4); c.printArea(); System.out.println("---------------"); c.setWidth(5); c.setHeight(6); c.printArea(); } } -------------------------------------------------------------------------------
リスト2は、引数に与えられたクラスの、Field、Method, Constructorをレポートする プログラムです。ここでは、 cls = Class.forName(argv[0]);という表現に注意して 下さい。ここで使われている getDeclaredFields(),getDeclaredMethods(), getDeclaredConstructors()と対を成す、getFields(),getMethods(),getConstructors() の違いも重要です。前者は、このクラスそのもので宣言・定義されている Field、Method, Constructorを、privateなものを含めてレポートしますが、上位の クラスから継承したものは一切レポートしません。後者は、上位のクラスから継承した ものを含め、全てのpublicな Field、Method, Constructorをレポートします。しかし、 publicでないものは全て除かれます。
---< リスト 2 >---------------------------------------------------------------- import java.lang.reflect.*; import java.beans.*; public class report { public static void main(String argv[]) { if (argv.length != 1) { throw new Error("Bad args"); } Class cls; try { cls = Class.forName(argv[0]); } catch (Exception ex) { System.out.println("Could not insantiate class " + argv[0]); ex.printStackTrace(); return; } try { System.out.println("\nFields:"); Field fields[] = cls.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { System.out.println(" " + fields[i].toString()); } System.out.println(""); System.out.println("Methods:"); Method methods[] = cls.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { System.out.println(" " + methods[i].toString()); } System.out.println(""); System.out.println("Constructors:"); Constructor constructors[] = cls.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) { System.out.println(" " + constructors[i].toString()); } System.out.println(""); } catch (Exception ex) { System.out.println("Caught exception: " + ex); ex.printStackTrace(); } } } -------------------------------------------------------------------------------
次の出力は、先に作成した reportクラスを、java report Rectangle として、引数に Rectangleを与えて実行してものです。カレント・ディレクトリに、reportクラスと Rectangleクラスが見えていると想定しています。 java report java.awt.Frame とか、java report java.util.Hashtable とか、色々 なクラスに対して、実行してみてください。簡単なAPIドキュメントが出来ることが 分かると思います。このreportプログラムを、先に触れた declared を抜かした メソッドを使ったものに変えれば、あるクラスで利用可能な Field、Methodの長大な一覧が得られることになります。awtクラスの中では最も単純な java report java.awt.Labelの出力が、百数十項目にもおよぶのを見れば、クラスを 継承するというプログラム・スタイルの強力さが、実感できるかもしれません。
Fields: public int Rectangle.width public int Rectangle.height Methods: public int Rectangle.area() public void Rectangle.setWidth(int) public int Rectangle.getWidth() public void Rectangle.setHeight(int) public int Rectangle.getHeight() public void Rectangle.printArea() public static void Rectangle.main(java.lang.String[]) Constructors: public Rectangle(int,int) public Rectangle()
クラス・オブジェクトからFieldオブジェクト、Methodオブジェクト、Constructor オブジェクトを得るために、getField,getMethod,getConstructorメソッドを使います。 クラスのFieldは、その名前で一意に決まりますので、getFieldメソッドの引数は フィールドをあらわす文字列です。一方、クラスからメソッドを獲得するには、 同じ名前でも、引数の数と型が異なるメソッドがありえますので、getMethodメソッドの 引数には、名前と引数の型が必要です。クラスのコンストラクタについては、メソッド と同じと思うかもしれませんが、コンストラクタの名前はクラスの名前と同じですの で、getConstructorメソッドの引数としては、引数の型のみが必要です。
それでは、引数の型をあらわすには、どのようにすればいいのでしょう。 そのためには、Classクラスの配列を用います。この時注意して欲しいのは、型を表現 するのに、プリミティブ・タイプの場合とそれ以外のクラスの場合では、あらわしかた が異なるということです。プリミティブ・タイプの場合には、*.TYPEという形を、それ 以外の場合には、*.classという形を用います。また、プリミティブ・タイプの場合に は、そのwrapperクラスの名前が、* の部分に入ります。ですから、int 二つを引数に 持つメソッドの引数の型をあらわすには、次のような配列を使います。 その次の例は、引数に文字列とintを取るメソッドの引数の型をあらわしています。
Class parameterType1[] = { Integer.TYPE, Integer.TYPE }; Class parameterType2[] = { String.class, Integer.TYPE };引数を持たないメソッドの型は、長さが0の引数の型の配列で指定します、 問題は、mainのように、配列を引数に取る場合です。うまい方法が思い付かない のですが、いくつかのメソッドを組み合わせれば、なんとか表現は出来ます。
Class Void[] ={} ; Class mainType[] = { Array.newInstance(String.class,0).getClass() };
あるオブジェクトobjのフィールド field の値の獲得には、Fieldクラスのgetメソッド を field.get(obj)という形で使います。このgetメソッドはObjectインスタンスを返す ので、適当な型にキャストする必要があります。フィールドがプリミティブ・タイプの 場合には、そのwrapperクラスのインスタンスが帰ります。実は、それぞれの プリミティブ・タイプについて、フィールドの値を獲得するメソッドが用意されてい ます。
public native Object get(Object obj) public native boolean getBoolean(Object obj) public native byte getByte(Object obj) public native char getChar(Object obj) public native short getShort(Object obj) public native int getInt(Object obj) public native long getLong(Object obj) public native float getFloat(Object obj) public native double getDouble(Object obj)
あるオブジェクトobjのフィールド field に値valueを設定するには、Fieldクラスのset メソッドを、field.set(obj,value)という形で使います。この他にも、それぞれの プリミティブ・タイプについてフィールドに値を設定する次のようなメソッドが あります。
public native void set(Object obj, Object value) public native void setBoolean(Object obj, boolean z) public native void setByte(Object obj, byte b) public native void setChar(Object obj, char c) public native void setShort(Object obj, short s) public native void setInt(Object obj, int i) public native void setLong(Object obj, long l) public native void setFloat(Object obj, float f) public native void setDouble(Object obj, double d)
あるオブジェクトobjのメソッド method に、引数 arg を与えて呼び出すためには、 Methodクラスの invokeメソッドを、method.invoke(obj,arg)という形で使います。 メソッドに渡す引数をあらわすには、Objectクラスの配列を用います。ですから、 プリミティブ・タイプの引数をメソッドに渡すためには、そのwrapperクラスを利用 します。
引数を持たないメソッドの引数は、長さ0のObjectの配列で表現します。 それでは、先に見た、String配列を引数に持つmainを、引数無しで呼ぶにはどうすれば いいのでしょうか? この場合は、「引数無し」といっても、もともとメソッド自体が 引数を持たないという訳ではありません。正確に言えば、mainメソッド自体は、String 配列という一つの引数をもつのですが、たまたまそのString配列の長さが0であると いうことなのです。
// 引数なしのメソッドに渡される引数VOID。 Object VOID[] = {}; // 引数無しで、mainを呼ぶときの引数NULL。 String nonArg[] = {}; Object NULL[] = { nonArg }; Object NULL[] = { null } ; // これでもいい。
コンストラクタの呼び出しには、invokeではなく、newInstanceメソッドを用います。 コンストラクタに与えられる引数の表現は、メソッドの場合と同じで、Objectの 配列を用います。