Java ReflectionAPIを詳しく見てみましょう

先月の「JavaIn-Depth」では、生のクラスデータにアクセスできるJavaクラスがクラスの「内部」を調べ、クラスがどのように構築されたかを理解する方法について説明しました。さらに、クラスローダーを追加することで、それらのクラスを実行環境にロードして実行できることを示しました。その例は、静的な内省の一形態です。今月は、Javaクラスに動的イントロスペクションを実行する機能(すでにロードされているクラスの内部を調べる機能)を提供するJava ReflectionAPIを見ていきます。

内省の効用

Javaの強みの1つは、Javaが実行されている環境が動的に変化することを想定して設計されていることです。クラスは動的にロードされ、バインディングは動的に実行され、オブジェクトインスタンスは必要に応じてオンザフライで動的に作成されます。歴史的にあまり動的ではなかったのは、「匿名」クラスを操作する機能です。このコンテキストでは、匿名クラスは、実行時にJavaクラスにロードまたは提示され、そのタイプが以前はJavaプログラムに認識されていなかったクラスです。

匿名クラス

匿名クラスをサポートすることは、説明するのが難しく、プログラムで設計するのがさらに困難です。匿名クラスをサポートするという課題は、次のように述べることができます。「Javaオブジェクトが与えられると、そのオブジェクトを継続的な操作に組み込むことができるプログラムを作成してください。」一般的な解決策はかなり難しいですが、問題を制限することにより、いくつかの特殊な解決策を作成できます。 1.0バージョンのJavaには、このクラスの問題に対する特殊な解決策の2つの例があります。JavaアプレットとコマンドラインバージョンのJavaインタープリターです。

Javaアプレットは、Webブラウザのコンテキストで実行中のJava仮想マシンによってロードされて呼び出されるJavaクラスです。これらのJavaクラスは匿名です。これは、ランタイムが個々のクラスを呼び出すために必要な情報を事前に知らないためです。ただし、特定のクラスを呼び出す問題は、Javaクラスを使用して解決されますjava.applet.Applet

のような一般的なスーパークラス、AppletおよびのようなJavaインターフェイスはAppletContext、事前に合意されたコントラクトを作成することにより、匿名クラスの問題に対処します。具体的には、ランタイム環境のサプライヤは、指定されたインターフェイスに準拠する任意のオブジェクトを使用できることをアドバタイズし、ランタイム環境のコンシューマは、ランタイムに提供する予定の任意のオブジェクトでその指定されたインターフェイスを使用します。アプレットの場合、明確に指定されたインターフェイスが共通のスーパークラスの形式で存在します。

一般的なスーパークラスソリューションの欠点は、特に多重継承がない場合、そのシステムがコントラクト全体を実装しない限り、環境で実行するように構築されたオブジェクトを他のシステムでも使用できないことです。以下の場合はAppletインターフェース、ホスティング環境を実装する必要がありますAppletContext。これがアプレットソリューションにとって意味することは、アプレットをロードしているときにのみソリューションが機能するということです。HashtableオブジェクトのインスタンスをWebページに配置し、ブラウザでそのインスタンスをポイントすると、アプレットシステムが制限された範囲外で動作できないため、ロードに失敗します。

アプレットの例に加えて、イントロスペクションは、先月述べた問題の解決に役立ちます。Java仮想マシンのコマンドラインバージョンがロードしたばかりのクラスで実行を開始する方法を理解することです。この例では、仮想マシンはロードされたクラスの静的メソッドを呼び出す必要があります。慣例により、そのメソッドには名前が付けられmain、単一の引数(Stringオブジェクトの配列)を取ります。

より動的なソリューションの動機

既存のJava1.0アーキテクチャの課題は、ロード可能なUIコンポーネント、JavaベースのOSのロード可能なデバイスドライバー、動的に構成可能な編集環境など、より動的なイントロスペクション環境で解決できる問題があることです。 「キラーアプリ」、つまりJava Reflection APIが作成される原因となった問題は、Javaのオブジェクトコンポーネントモデルの開発でした。そのモデルは現在、JavaBeansとして知られています。

ユーザーインターフェイスコンポーネントは、2つの非常に異なるコンシューマーを持っているため、イントロスペクションシステムの理想的な設計ポイントです。一方では、コンポーネントオブジェクトがリンクされて、アプリケーションの一部としてユーザーインターフェイスを形成します。あるいは、コンポーネントが何であるかを知らなくても、またはさらに重要なことに、コンポーネントのソースコードにアクセスせずに、ユーザーコンポーネントを操作するツールのインターフェイスが必要です。

Java Reflection APIは、JavaBeansユーザーインターフェイスコンポーネントAPIのニーズから生まれました。

リフレクションとは何ですか?

基本的に、Reflection APIは、クラスファイルのさまざまな部分を表すオブジェクトと、それらのオブジェクトを安全かつ確実な方法で抽出する手段の2つのコンポーネントで構成されています。Javaは多くのセキュリティ保護手段を提供し、それらの保護手段を無効にするクラスのセットを提供することは意味がないため、後者は非常に重要です。

Reflection APIの最初のコンポーネントは、クラスに関する情報をフェッチするために使用されるメカニズムです。このメカニズムは、という名前のクラスに組み込まれていClassます。特別なクラスClassは、Javaシステム内のオブジェクトを記述するメタ情報のユニバーサルタイプです。Javaシステムのクラスローダーは、タイプがオブジェクトを返しますClass。これまで、このクラスで最も興味深い3つのメソッドは次のとおりです。

  • forName、現在のクラスローダーを使用して、指定された名前のクラスをロードします

  • getName、クラスの名前をStringオブジェクトとして返します。これは、クラス名でオブジェクト参照を識別するのに役立ちました。

  • newInstance、クラスのnullコンストラクター(存在する場合)を呼び出し、そのオブジェクトのクラスのオブジェクトインスタンスを返します

これらの3つの便利なメソッドに、ReflectionAPIはクラスにいくつかの追加メソッドを追加しますClass。これらは次のとおりです。

  • getConstructorgetConstructorsgetDeclaredConstructor
  • getMethodgetMethodsgetDeclaredMethods
  • getFieldgetFieldsgetDeclaredFields
  • getSuperclass
  • getInterfaces
  • getDeclaredClasses

これらのメソッドに加えて、これらのメソッドが返すオブジェクトを表すために、多くの新しいクラスが追加されました。新しいクラスは、大部分の一部であるjava.lang.reflectパッケージが、新しい基本型クラスの一部は(VoidByte、およびになるよう)にあるjava.langパッケージ。メタデータを表すクラスをリフレクションパッケージに入れ、タイプを表すクラスを言語パッケージに入れることで、新しいクラスを現在の場所に配置することが決定されました。

したがって、Reflection APIClassは、クラスの内部について質問できるようにするクラスへの多数の変更と、これらの新しいメソッドが提供する回答を表す一連のクラスを表します。

Reflection APIを使用するにはどうすればよいですか?

「APIを使用するにはどうすればよいですか?」という質問。おそらく「リフレクションとは何か」よりも興味深い質問です。

Reflection APIは対称的です。つまり、Classオブジェクトを保持している場合はその内部について尋ねることができ、内部の1つがある場合は、どのクラスがそれを宣言したかを尋ねることができます。したがって、クラスからメソッド、パラメータ、クラス、メソッドなどを行ったり来たりすることができます。このテクノロジーの興味深い使用法の1つは、特定のクラスとシステムの他の部分との間の相互依存性のほとんどを見つけることです。

実例

ただし、より実用的なレベルでは、dumpclass先月のコラムで私のクラスが行ったように、ReflectionAPIを使用してクラスをダンプすることができます。

Reflection APIをデモンストレーションするReflectClassために、Javaランタイムに認識されているクラス(クラスパスのどこかにあることを意味します)を取得し、ReflectionAPIを介してその構造をターミナルウィンドウにダンプするというクラスを作成しました。このクラスを試すには、1.1バージョンのJDKを使用できるようにする必要があります。

注:1.0ランタイムを使用しようとないでください。すべてが混乱し、通常、互換性のないクラス変更例外が発生します。

クラスReflectClassは次のように始まります。

importjava.lang.reflect。*; インポートjava.util。*; パブリッククラスReflectClass {

上記のように、コードが最初に行うことは、ReflectionAPIクラスをインポートすることです。次に、以下に示すように開始するmainメソッドに直接ジャンプします。

public static void main(String args []){コンストラクタcn [];クラスcc [];メソッドmm [];フィールドff [];クラスc = null;クラスsupClass;文字列x、y、s1、s2、s3; Hashtable classRef = new Hashtable(); if(args.length == 0){System.out.println( "コマンドラインでクラス名を指定してください。"); System.exit(1); } try {c = Class.forName(args [0]); } catch(ClassNotFoundException ee){System.out.println( "クラスが見つかりませんでした '" + args [0] + "'"); System.exit(1); }

このメソッドmainは、コンストラクター、フィールド、およびメソッドの配列を宣言します。思い出してください。これらは、クラスファイルの4つの基本的な部分のうちの3つです。4番目の部分は属性ですが、残念ながらReflectionAPIではアクセスできません。配列の後で、いくつかのコマンドライン処理を実行しました。ユーザーがクラス名を入力した場合、コードforNameはクラスのメソッドを使用してクラス名をロードしようとしますClass。このforNameメソッドはファイル名ではなくJavaクラス名を使用するため、java.math.BigIntegerクラスの内部を調べるには、クラスファイルが実際に格納されている場所を指定するのではなく、単に「javaReflectClassjava.math.BigInteger」と入力します。

クラスのパッケージを特定する

クラスファイルが見つかったとすると、コードはステップ0に進みます。これを以下に示します。

 /* * Step 0: If our name contains dots we're in a package so put * that out first. */ x = c.getName(); y = x.substring(0, x.lastIndexOf(".")); if (y.length() > 0) { System.out.println("package "+y+";\n\r"); } 

In this step, the name of the class is retrieved using the getName method in class Class. This method returns the fully qualified name, and if the name contains dots, we can presume that the class was defined as part of a package. So Step 0 is to separate the package name part from the class name part, and print out the package name part on a line that starts with "package...."

Collecting class references from declarations and parameters

With the package statement taken care of, we proceed to Step 1, which is to collect all of the other class names that are referenced by this class. This collection process is shown in the code below. Remember that the three most common places where class names are referenced are as types for fields (instance variables), return types for methods, and as the types of the parameters passed to methods and constructors.

 ff = c.getDeclaredFields(); for (int i = 0; i < ff.length; i++) { x = tName(ff[i].getType().getName(), classRef); } 

In the above code, the array ff is initialized to be an array of Field objects. The loop collects the type name from each field and process it through the tName method. The tName method is a simple helper that returns the shorthand name for a type. So java.lang.String becomes String. And it notes in a hashtable which objects have been seen. At this stage, the code is more interested in collecting class references than in printing.

The next source of class references are the parameters supplied to constructors. The next piece of code, shown below, processes each declared constructor and collects the references from the parameter lists.

 cn = c.getDeclaredConstructors(); for (int i = 0; i  0) { for (int j = 0; j < cx.length; j++) { x = tName(cx[j].getName(), classRef); } } } 

As you can see, I've used the getParameterTypes method in the Constructor class to feed me all of the parameters that a particular constructor takes. These are then processed through the tName method.

An interesting thing to note here is the difference between the method getDeclaredConstructors and the method getConstructors. Both methods return an array of constructors, but the getConstructors method only returns those constructors that are accessible to your class. This is useful if you want to know if you actually can invoke the constructor you've found, but it isn't useful for this application because I want to print out all of the constructors in the class, public or not. The field and method reflectors also have similar versions, one for all members and one only for public members.

以下に示す最後のステップは、すべてのメソッドから参照を収集することです。このコードは、メソッドのタイプ(上記のフィールドと同様)とパラメーター(上記のコンストラクターと同様)の両方から参照を取得する必要があります。

mm = c.getDeclaredMethods(); for(int i = 0; i 0){for(int j = 0; j <cx.length; j ++){x = tName(cx [j] .getName()、classRef); }}}

上記のコードでは、2つの呼び出しがあります。1つはtName戻り値の型を収集するためのもので、もう1つは各パラメーターの型を収集するためのものです。