Javaリフレクションの深い理解
反射の原理を理解するためには、まず情報の種類を理解する必要があります。オブジェクトとクラス、2つの方法があります識別しましょうJavaランタイム情報:1我々はすでに、すべてのコンパイル時の型情報で知っていると仮定し、伝統的なRTTI、であるが、他の私たちを可能にする反射機構であります見つけると、実行時にクラスに関する情報を使用しています。
1、クラスオブジェクト
RTTIはJavaで、あなたが最初にどのように表現するか、これはクラスに関する情報が含まれているClassオブジェクトによって行われている実行時の情報の種類を知っておく必要があります方法を理解します。Classオブジェクトはすべて「正常な」オブジェクトを作成するために使用され、Javaはあなたが同様の型変換などの操作を実行している場合でも、クラスを使用してRTTIを実行するオブジェクト。
各クラスには、対応するClassオブジェクトを持つことになります、の.classファイルに格納されます。すべてのクラスは、あなたがそれを初めて使用するとき、動的にプログラムを参照のクラスの静的メンバを作成するときに、JVMにロードにあり、それはクラスをロードします。必要なときにクラスのオブジェクトのみがロードされたクラスがロードされるときに、静的な初期化が行われます。
パブリッククラスいるTestMain { パブリック静的無効メイン(文字列[] args){ のSystem.out.println(XYZ.name)。 } } クラスXYZ { =「luoxn28」パブリック静的文字列名。 静的{ のSystem.out.println( "XYZ静态块")。 } パブリックXYZ(){ System.out.printlnは( "XYZ构造了")。 } }
出力は次のようになります。
すでにロードされていない場合は、このクラスのClassオブジェクトは、以前にロードされているかどうかをクラスローダ最初のチェックは、デフォルトのクラスローダは、クラス名に基づいて対応する.classファイルを探します。
実行時型情報を使用したい、あなたはこの目的を達成するため、またはbase.classを使用することができる機能にClass.forName(「ベース」)を使用し、Classオブジェクトの参照(クラスベースのオブジェクトとして)オブジェクトを取得する必要があります。非常に興味深いがあることに注意してください、参照が自動的にforNameを使用して、Classオブジェクトを初期化していないときに、クラスオブジェクトを作成する機能「の.class」を使用()が自動的にClassオブジェクトを初期化します。準備作業を行うためにクラスを使用するためには、一般的に次の3つのステップがあります。
- 負荷:クラスローダによって、見つけるために、対応するバイトコードは、クラスのオブジェクトを作成します
- リンク:クラスのバイトコード検証、静的フィールドに割り当てられた領域
- 初期化:クラスは、スーパークラスを持っている場合、その初期化は、静的初期化及び静的初期化ブロックを実行します
基本クラス{パブリック 静的INT NUM = 1; 静的{ するSystem.out.println( "ベース" + NUM); } } publicクラスメイン{ パブリック静的無効メイン(文字列[] args){ //静的ブロック初期化されない クラスclazz1の= Base.class; のSystem.out.println( "------"); //は、初期化 クラス= Class.forNameの( "zzz.Base")をclazz2; } }
2は、確認する前に最初のキャストを行います
合法ではない例外をスローする場合、コンパイラは、意気消沈の種類の合法性をチェックします。ダウンコンバージョンタイプの前に、使用する分析のinstanceof。
クラスBase {} 派生クラスは、Base {}延び パブリッククラスメイン{ パブリック静的無効メイン(文字列[] args){ 誘導基基地=新規の(); IF(ベースはinstanceofの派生){ //这里可以向下转换了 するSystem.out.println( "OK")。 } 他{ するSystem.out.println( "OKではありません")。 } } }
3、反射:ランタイムクラス情報
あなたがオブジェクトの正確な種類がわからない場合は、RTTIはあなたを伝えることができますが、前提があります:このタイプは、それを識別するためにRTTIを使用するためには、コンパイル時に認識されなければなりません。サポートのjava.lang.reflectの反射と一緒にクラスのクラスライブラリは、ライブラリーは、未知のクラスに対応する部材を示すために、フィールド、メソッドやコンストラクタクラス、JVMの起動によって作成されたオブジェクトのこれらのクラスを含みます。この場合、あなたが呼び出す使用して、コンストラクタがフィールドオブジェクトのget()とset()メソッドに関連付けられたクラスのフィールドを、新しいオブジェクトを作成、取得および変更に使用することができます()メソッドは、メソッドのオブジェクトに関連付けられたメソッドを呼び出します。加えて、リターンにフィールド、メソッド、およびコンストラクタのアレイをれるGetFields()、getMethods()およびgetConstructors()と多くの便利なメソッドを呼び出すオブジェクトができるので、オブジェクト情報は、完全に実行時に定義することができる、及びコンパイル時に、あなたは、クラスについて何を知っている必要はありません。
未知のタイプの物体によって反射を扱うときに反射し、魔法はありませんが、JVMは、単にそれが属する特定のどのクラスを参照するために、オブジェクトをチェックします。したがって、クラス.class
JVMについては、いずれか、またはローカルマシン上のネットワークから取得し、アクセス可能でなければなりません。現実とのみからなる反射RTTIの違いのためにそう:
- RTTIは、コンパイラが開き、コンパイル時にチェックの.classファイル
- 反射、オープンおよびランタイムの.classファイルをチェック
パブリッククラスPersonはSerializable {実装 プライベート文字列名を、 プライベートint型の年齢; //取得/設定方法 } パブリック静的無効メイン(文字列[] args){ 人の人物=新しい人物( "luoxn28"、23); クラスclazz = person.getClass()。 フィールド[]フィールド= clazz.getDeclaredFields()。 (項目フィールド:フィールド)のための{ 文字列キー= field.getName()。 PropertyDescriptorをディスクリプタ=新しいのPropertyDescriptor(キー、clazz); 方法メソッド= descriptor.getReadMethod()。 オブジェクト値= method.invoke(人)。 System.out.println(キー+ ":" +値)。 } }
上記getReadMethodによって()クラスメソッドは、関数がgetWriteMethod()メソッド設定クラスメソッドによって呼び出されることができる呼び出し得ます。一般的に、我々は反射ツールを使用する必要はありませんが、彼らは動的なコードを作成しているに言えば、その上、このような連載のJavaBeanオブジェクトとしてJavaで他の機能をサポートするために、より便利反射、可能となります。
4、動的プロキシ
追加のまたは異なる動作を提供し、挿入されたオブジェクト「本当の」オブジェクトを置き換えるために使用するためにプロキシ・モードでは、これらの動作は、通信「本当の」オブジェクトを含み、従って、仲介エージェント典型的役割として働きます。Javaの動的プロキシエージェント歩前進アイデアよりも、それが動的に作成し、プロキシと動的プロキシアプローチへのコールを処理することができます。動的プロキシで行われたすべてのコールは、単一にリダイレクトする呼処理作業は、コールのタイプを明らかにし、適切な戦略を決定するためにされています。以下は、動的プロキシの一例です。
インタフェースと実装クラス:
パブリックインターフェイスインターフェイス{ ボイドのdoSomething(); 無効somethingElse(String型の引数); } publicクラスRealObjectインターフェイス{実装 公共ボイドのdoSomething(){ System.out.printlnは( "doSomethingのは。"); } 公共ボイドsomethingElse(文字列引数){ するSystem.out.println( "somethingElse" +引数)。 } }
動的プロキシ・オブジェクト・ハンドラー:
パブリッククラスDynamicProxyHandlerはのInvocationHandlerを実装{ プライベートオブジェクトがproxyed。 公共DynamicProxyHandler(オブジェクトproxyed){ this.proxyed = proxyed。 } @Override パブリックオブジェクトを呼び出し(オブジェクトプロキシ、方法方法、[]引数オブジェクト)IllegalAccessExceptionが、はIllegalArgumentException、にInvocationTargetException {スロー するSystem.out.println( "代理工作了します。")。 method.invoke(proxyed、引数)を返します。 } }
テストカテゴリ:
パブリッククラスメイン{ パブリック静的無効メイン(文字列[] args){ RealObject =実新RealObject()。 インターフェイスプロキシ=(インターフェース)たとえば、Proxy.newProxyInstance( Interface.class.getClassLoader()、新しいクラス[] {} Interface.class、 新しいDynamicProxyHandler(実))。 proxy.doSomething(); proxy.somethingElse( "luoxn28"); } }
出力:
プロキシは、静的メソッドたとえば、Proxy.newProxyInstance()動的プロキシを呼び出すことによって作成することができ、この方法は、あなたが達成したいクラスのクラスローダ、エージェント(ないクラスや抽象クラス)に実装されたインタフェースのリスト、およびのInvocationHandlerにする必要があります。ダイナミックプロキシがコール・プロセッサへのすべてのコールをリダイレクトすることができ、それは多くの場合、コンストラクタプロセッサは仲介プロセッサタスクを実行するために呼び出されます「本物の」オブジェクトへの参照を渡す呼び出しされ、その要求を転送します。
反射の原理を理解するためには、まず情報の種類を理解する必要があります。オブジェクトとクラス、2つの方法があります識別しましょうJavaランタイム情報:1我々はすでに、すべてのコンパイル時の型情報で知っていると仮定し、伝統的なRTTI、であるが、他の私たちを可能にする反射機構であります見つけると、実行時にクラスに関する情報を使用しています。
1、クラスオブジェクト
RTTIはJavaで、あなたが最初にどのように表現するか、これはクラスに関する情報が含まれているClassオブジェクトによって行われている実行時の情報の種類を知っておく必要があります方法を理解します。Classオブジェクトはすべて「正常な」オブジェクトを作成するために使用され、Javaはあなたが同様の型変換などの操作を実行している場合でも、クラスを使用してRTTIを実行するオブジェクト。
各クラスには、対応するClassオブジェクトを持つことになります、の.classファイルに格納されます。すべてのクラスは、あなたがそれを初めて使用するとき、動的にプログラムを参照のクラスの静的メンバを作成するときに、JVMにロードにあり、それはクラスをロードします。必要なときにクラスのオブジェクトのみがロードされたクラスがロードされるときに、静的な初期化が行われます。
パブリッククラスいるTestMain { パブリック静的無効メイン(文字列[] args){ のSystem.out.println(XYZ.name)。 } } クラスXYZ { =「luoxn28」パブリック静的文字列名。 静的{ のSystem.out.println( "XYZ静态块")。 } パブリックXYZ(){ System.out.printlnは( "XYZ构造了")。 } }
出力は次のようになります。
すでにロードされていない場合は、このクラスのClassオブジェクトは、以前にロードされているかどうかをクラスローダ最初のチェックは、デフォルトのクラスローダは、クラス名に基づいて対応する.classファイルを探します。
実行時型情報を使用したい、あなたはこの目的を達成するため、またはbase.classを使用することができる機能にClass.forName(「ベース」)を使用し、Classオブジェクトの参照(クラスベースのオブジェクトとして)オブジェクトを取得する必要があります。非常に興味深いがあることに注意してください、参照が自動的にforNameを使用して、Classオブジェクトを初期化していないときに、クラスオブジェクトを作成する機能「の.class」を使用()が自動的にClassオブジェクトを初期化します。準備作業を行うためにクラスを使用するためには、一般的に次の3つのステップがあります。
- 負荷:クラスローダによって、見つけるために、対応するバイトコードは、クラスのオブジェクトを作成します
- リンク:クラスのバイトコード検証、静的フィールドに割り当てられた領域
- 初期化:クラスは、スーパークラスを持っている場合、その初期化は、静的初期化及び静的初期化ブロックを実行します
基本クラス{パブリック 静的INT NUM = 1; 静的{ するSystem.out.println( "ベース" + NUM); } } publicクラスメイン{ パブリック静的無効メイン(文字列[] args){ //静的ブロック初期化されない クラスclazz1の= Base.class; のSystem.out.println( "------"); //は、初期化 クラス= Class.forNameの( "zzz.Base")をclazz2; } }
2は、確認する前に最初のキャストを行います
合法ではない例外をスローする場合、コンパイラは、意気消沈の種類の合法性をチェックします。ダウンコンバージョンタイプの前に、使用する分析のinstanceof。
クラスBase {} 派生クラスは、Base {}延び パブリッククラスメイン{ パブリック静的無効メイン(文字列[] args){ 誘導基基地=新規の(); IF(ベースはinstanceofの派生){ //这里可以向下转换了 するSystem.out.println( "OK")。 } 他{ するSystem.out.println( "OKではありません")。 } } }
3、反射:ランタイムクラス情報
あなたがオブジェクトの正確な種類がわからない場合は、RTTIはあなたを伝えることができますが、前提があります:このタイプは、それを識別するためにRTTIを使用するためには、コンパイル時に認識されなければなりません。サポートのjava.lang.reflectの反射と一緒にクラスのクラスライブラリは、ライブラリーは、未知のクラスに対応する部材を示すために、フィールド、メソッドやコンストラクタクラス、JVMの起動によって作成されたオブジェクトのこれらのクラスを含みます。この場合、あなたが呼び出す使用して、コンストラクタがフィールドオブジェクトのget()とset()メソッドに関連付けられたクラスのフィールドを、新しいオブジェクトを作成、取得および変更に使用することができます()メソッドは、メソッドのオブジェクトに関連付けられたメソッドを呼び出します。加えて、リターンにフィールド、メソッド、およびコンストラクタのアレイをれるGetFields()、getMethods()およびgetConstructors()と多くの便利なメソッドを呼び出すオブジェクトができるので、オブジェクト情報は、完全に実行時に定義することができる、及びコンパイル時に、あなたは、クラスについて何を知っている必要はありません。
未知のタイプの物体によって反射を扱うときに反射し、魔法はありませんが、JVMは、単にそれが属する特定のどのクラスを参照するために、オブジェクトをチェックします。したがって、クラス.class
JVMについては、いずれか、またはローカルマシン上のネットワークから取得し、アクセス可能でなければなりません。現実とのみからなる反射RTTIの違いのためにそう:
- RTTIは、コンパイラが開き、コンパイル時にチェックの.classファイル
- 反射、オープンおよびランタイムの.classファイルをチェック
パブリッククラスPersonはSerializable {実装 プライベート文字列名を、 プライベートint型の年齢; //取得/設定方法 } パブリック静的無効メイン(文字列[] args){ 人の人物=新しい人物( "luoxn28"、23); クラスclazz = person.getClass()。 フィールド[]フィールド= clazz.getDeclaredFields()。 (項目フィールド:フィールド)のための{ 文字列キー= field.getName()。 PropertyDescriptorをディスクリプタ=新しいのPropertyDescriptor(キー、clazz); 方法メソッド= descriptor.getReadMethod()。 オブジェクト値= method.invoke(人)。 System.out.println(キー+ ":" +値)。 } }
上記getReadMethodによって()クラスメソッドは、関数がgetWriteMethod()メソッド設定クラスメソッドによって呼び出されることができる呼び出し得ます。一般的に、我々は反射ツールを使用する必要はありませんが、彼らは動的なコードを作成しているに言えば、その上、このような連載のJavaBeanオブジェクトとしてJavaで他の機能をサポートするために、より便利反射、可能となります。
4、動的プロキシ
追加のまたは異なる動作を提供し、挿入されたオブジェクト「本当の」オブジェクトを置き換えるために使用するためにプロキシ・モードでは、これらの動作は、通信「本当の」オブジェクトを含み、従って、仲介エージェント典型的役割として働きます。Javaの動的プロキシエージェント歩前進アイデアよりも、それが動的に作成し、プロキシと動的プロキシアプローチへのコールを処理することができます。動的プロキシで行われたすべてのコールは、単一にリダイレクトする呼処理作業は、コールのタイプを明らかにし、適切な戦略を決定するためにされています。以下は、動的プロキシの一例です。
インタフェースと実装クラス:
パブリックインターフェイスインターフェイス{ ボイドのdoSomething(); 無効somethingElse(String型の引数); } publicクラスRealObjectインターフェイス{実装 公共ボイドのdoSomething(){ System.out.printlnは( "doSomethingのは。"); } 公共ボイドsomethingElse(文字列引数){ するSystem.out.println( "somethingElse" +引数)。 } }
動的プロキシ・オブジェクト・ハンドラー:
public class DynamicProxyHandler implements InvocationHandler { private Object proxyed; public DynamicProxyHandler(Object proxyed) { this.proxyed = proxyed; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { System.out.println("代理工作了."); return method.invoke(proxyed, args); } }
测试类:
パブリッククラスメイン{ パブリック静的無効メイン(文字列[] args){ RealObject =実新RealObject()。 インターフェイスプロキシ=(インターフェース)たとえば、Proxy.newProxyInstance( Interface.class.getClassLoader()、新しいクラス[] {} Interface.class、 新しいDynamicProxyHandler(実))。 proxy.doSomething(); proxy.somethingElse( "luoxn28"); } }
出力:
プロキシは、静的メソッドたとえば、Proxy.newProxyInstance()動的プロキシを呼び出すことによって作成することができ、この方法は、あなたが達成したいクラスのクラスローダ、エージェント(ないクラスや抽象クラス)に実装されたインタフェースのリスト、およびのInvocationHandlerにする必要があります。ダイナミックプロキシがコール・プロセッサへのすべてのコールをリダイレクトすることができ、それは多くの場合、コンストラクタプロセッサは仲介プロセッサタスクを実行するために呼び出されます「本物の」オブジェクトへの参照を渡す呼び出しされ、その要求を転送します。