JavaのRTTIとリフレクション機構の詳しい説明

黄舟
リリース: 2017-09-20 09:59:53
オリジナル
1270 人が閲覧しました

この記事では、主に Java の RTTI とリフレクション メカニズムのコード分析について説明し、その例とリフレクション メカニズムが使用される場面についても説明します。

RTTI、つまり、Run-Time Type Identification、実行時の型の識別です。実行時型の識別は、Java の実行時に非常に便利なメカニズムです。RTTI はクラス関連の情報を維持します。 RTTI は、コンパイル時に既知のすべての型を実行時に自動的に識別できます。

多くの場合、上方変換を実行する必要があります。たとえば、Base クラスは Derived クラスを派生しますが、既存のメソッドにはパラメーターとして Base オブジェクトのみが必要で、実際に渡されるのはその派生クラスへの参照です。 。このとき、RTTI が役割を果たします。たとえば、RTTI は、Derive クラスが Base の派生クラスであることを識別し、それを Derived に上方変換できます。同様に、インターフェイスをパラメータとして使用する場合、上向き変換がより一般的に使用され、RTTI はこの時点で上向き変換を実行できるかどうかを決定できます。

この型情報は、クラス関連情報を含む Class オブジェクト (java.lang.Class) の特別なオブジェクトを通じて完成されます。クラスが作成されコンパイルされるたびに、Class オブジェクトを保持する .class ファイルが生成されます。このプログラムを実行する Java 仮想マシン (JVM) は、クラス ローダー (クラス ローダー) と呼ばれるサブシステムを使用します。クラス ローダーは、プログラムの実行前にすべての Class オブジェクトをロードするわけではありません。ロードされていない場合、デフォルトのクラス ローダーはクラス名に基づいて .class ファイルを検索します (たとえば、追加のクラス ローダーが単語を検索する場合があります)。データベース内のバイトコード)、このクラスのバイトコードはロード時に検証され、破損していないか、不正な Java コードが含まれていないことが確認されます。これは Java のタイプ セーフティ メカニズムの 1 つでもあります。特定のクラスの Class オブジェクトがメモリにロードされると、そのクラスのすべてのオブジェクトを作成できます。


package typeinfo;
class Base {
  static { System.out.println("加载Base类"); }
}
class Derived extends Base { 
  static { System.out.println("加载Derived类");}
}
public class Test {
  static void printerInfo(Class c) {
    System.out.println("类名: " + c.getName() +
      "是否接口? [" + c.isInterface() + "]");
  }
  public static void main(String[] args) {
    Class c = null;
    try {
      c = Class.forName("typeinfo.Derived");
    } catch (ClassNotFoundException e) {
      System.out.println("找不到Base类");
      System.exit(1);
    }
    printerInfo(c);
    Class up = c.getSuperclass(); // 取得c对象的基类
    Object obj = null;
    try {
      obj = up.newInstance();
    } catch (InstantiationException e) {
      System.out.println("不能实例化");
      System.exit(1);
    } catch (IllegalAccessException e) {
      System.out.println("不能访问");
      System.exit(1);
    }
    printerInfo(obj.getClass());
  } /* 输出:
  加载Base类
  加载Derived类
  类名: typeinfo.Derived是否接口? [false]
  类名: typeinfo.Base是否接口? [false]
  */
}
ログイン後にコピー

上記のコードでは、forName メソッドは 静的メソッド であり、クラスが存在するかどうかを確認するために使用され、クラスが存在する場合はクラス参照が返されます。 ClassNotFoundException 例外がスローされます。

クラスがデフォルトのフォルダーではなく、特定のパッケージにある場合は、以前のパッケージ名を含める必要があります (ここでは typeinfo.Derived など)。

getSuperclass メソッドを使用して、基本クラスに対応する Class オブジェクトを返すことができます。 newInstance メソッドを使用して、デフォルトの構造に従ってインスタンス オブジェクトを作成し、インスタンス化できない場合やアクセスできない場合にそれをスローします。 InstantiationException および IllegalAccessException がスローされます。

Java は、Class オブジェクトへの参照、つまりクラス リテラルを生成する方法も提供します。上記のプログラムでは、up は Base.class に相当します。

基本データ型のラッパークラスの場合、char.class は Character.TYPE に相当し、int.class は Integer.TYPE に相当します。残りの ab.class は Ab.TYPE と同等です。 (たとえば、void.class は Void.TYP と同等です)。また、Java SE5からは、int.classとInteger.classも同じものになります。

一般化されたクラス参照、以下のコードを参照してください


    Class intClass = int.class;
    Class<Integer> genericIntClass = int.class;
    genericIntClass = Integer.class; // 等价
    intClass = double.class; // ok
    // genericIntClass = double.class; // Illegal!
ログイン後にコピー

Classオブジェクトの参照は Integer オブジェクトを指定するため、参照を double.class に指すことはできません。制限を緩和するには、ワイルドカード文字 ? (つまり、Class) を使用できます。効果は Class と同じですが、Class を使用しないことを意味します。偶然または過失により、非特定のクラス参照を使用した。同時に、継承するクラスを制限することもできます。例は次のとおりです。


class Base {}
class Derived extends Base {}
class Base2 {}
public class Test {
  public static void main(String[] args) {
    Class<? extends Base> cc = Derived.class; // ok
    // cc = Base2.class; // Illegal
  } 
}
ログイン後にコピー

クラス参照にジェネリック構文を追加する理由は、コンパイル時に型エラーを検出できるようにするためだけです。時間。

既知の RTTI フォームには次のものが含まれます。

1. RTTI は、不正な型変換が実行されると、ClassCastException をスローします。 Class オブジェクトはオブジェクトのタイプを表します。実行時に必要な情報は、Class オブジェクトをクエリする (つまり、Class クラスのメソッドを呼び出す) ことによって取得できます。

C++ の従来の型変換では RTTI を使用しません。詳細については、C++ の RTTI セクションを参照してください。 (余談ですが、私が C++ を勉強していたときに、何気なく RTTI の章をざっと見ただけでした。今思い出しますと、dynamic_cast などは、特にタイプ セーフティのために追加されていました。C++ は、Java と同じように、安全性の観点から選択性を提供できます。StringBuilder とStringBuffer では、安全性と効率性を同時に達成することはできませんか? Java は、式 x = 1 を暗黙的にブール型に変換できないのと同様に、型安全性の点でより必須です)。

Java には RTTI の 3 番目の形式があります。これは、オブジェクトが特定の型の例であるかどうかを示す
ブール値

を返すキーワード instanceof です。次のコードを参照してください。

class Base {}
class Derived extends Base {}
public class Test {
  public static void main(String[] args) {
    Derived derived = new Derived();
    System.out.println(derived instanceof Base); // 输出true
  } 
}
ログイン後にコピー

たとえば、基本クラス Shape はさまざまなクラス (Circle、Rectangle など) を派生させ、入力パラメータはすべての Circle に色を付ける必要があります。 Shape オブジェクトの束。instandof を使用して、Shape オブジェクトが Circle オブジェクトであるかどうかを判断できます。

RTTI可以识别程序空间的所有类,但是有时候需要从磁盘文件或网络文件中读取一串字节码,并且被告知这些字节代表一个类,就需要用到反射机制。

比如在IDE中创建图形化程序时会使用到一些控件,只需要从本地的控件对应class文件中读取即可,然后再主动修改这些控件的属性。(题外话:大概.net组件就是这样的?学C#时总听到反射,但总没感觉用过,前几天做.net项目的同学也跟我说他从来都没用过委托和事件……)

Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含Field、Method和Constructor类(每个类都实现了Member接口),这些类型的对象都是JVM在运行时创建的,用以表示未知类里对应成员。

这样就可以用Constructor创建未知对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke方法调用与Method对象关联的字段,等等。


// 使用反射展示类的所有方法, 即使方法是在基类中定义的
package typeinfo;
// Print类的print方法等价于System.Out.Println,方便减少代码量
import static xyz.util.Print.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
// {Args: typeinfo.ShowMethods}
public class ShowMethods {
  private static String usage = 
    "usage:\n" +
    "ShowMethods qualified.class.name\n" +
    "To show all methods in class or:\n" +
    "ShowMethods qualified.class.name word\n" +
    "To search for methods involving &#39;word&#39;";
  // 去掉类名前面的包名
  private static Pattern p = Pattern.compile("\\w+\\.");
  public static void main(String[] args) {
    if (args.length < 1) {
      print(usage);
      System.exit(0);
    }
    int lines = 0;
    try {
      Class<?> c = Class.forName(args[0]);
      // 反射获得对象c所属类的方法
      Method[] methods = c.getMethods();
      // 反射获得对象c所属类的构造
      Constructor[] ctors = c.getConstructors();
      if (args.length == 1) {
        for (Method method : methods)
          print(p.matcher(method.toString()).replaceAll(""));
        for (Constructor ctor : ctors)
          print(p.matcher(ctor.toString()).replaceAll(""));
      }
    } catch (ClassNotFoundException e) {
      print("No such class: " + e);
    }
  } /*
  public static void main(String[])
  public final void wait() throws InterruptedException
  public final void wait(long,int) throws InterruptedException
  public final native void wait(long) throws InterruptedException
  public boolean equals(Object)
  public String toString()
  public native int hashCode()
  public final native Class getClass()
  public final native void notify()
  public final native void notifyAll()
  public ShowMethods()
  */
}
ログイン後にコピー

简单来说,反射机制就是识别未知类型的对象。反射常用于动态代理中。举例如下:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class DynamicProxyHandler implements InvocationHandler {
  private Object proxied; // 代理对象
  public DynamicProxyHandler(Object proxied) {
    // TODO Auto-generated constructor stub
    this.proxied = proxied;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // TODO Auto-generated method stub
    System.out.println("代理类: " + proxy.getClass() + "\n"
        + "代理方法: " + method + "\n"
        + "参数: " + args);
    if (args != null)
      for (Object arg : args)
        System.out.println(" " + arg);
    return method.invoke(proxied, args);
  }
}
interface Interface { void doSomething(); }

class RealObject implements Interface {
  
  @Override
  public void doSomething() {
    // TODO Auto-generated method stub
    System.out.println("doSomething");
  }
}
public class DynamicProxyDemo {
  public static void consumer(Interface iface) {
    iface.doSomething();
  }
  public static void main(String[] args) {
    RealObject realObject = new RealObject();
    // 使用动态代理
    Interface proxy = (Interface)Proxy.newProxyInstance(
        Interface.class.getClassLoader(),
        new Class[] { Interface.class }, 
        new DynamicProxyHandler(realObject));
    consumer(proxy);
  } /* 输出:
  代理类: class $Proxy0
  代理方法: public abstract void Interface.doSomething()
  参数: null
  doSomething
  */
}
ログイン後にコピー

代理是基本的设计模式之一,即用代理类为被代理类提供额外的或不同的操作。而动态代理则需要一个类加载器,就像Java实现RTTI时需要类加载器加载类的信息,这样就可以知道类的相关信息。

关键方法是:

Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException

传入三个参数:代理接口的加载器(通过Class对象的getClassLoader方法获取),代理的方法接口,代理对象

前两个参数很好理解,就是要代理的方法所属的接口对应的Class对象(主语)的加载器和Class对象本身,主要是参数3,要设计一个实现InvocationHandler接口的类,作为代理对象,一般命名以Handler结尾,Handler翻译为处理者,很形象,就是代替原对象进行处理的处理者(即代理),在程序设计中经常被翻译成“句柄”。

这个类通过传入代理对象来构造,比如这里传入的是Object对象。然后必须覆盖invoke方法。

通过最后输出和invoke方法的具体实现可以发现,return method.invoke(proxied, args);是相当于原对象调用该方法(类似C++的回调函数?)

由于有类加载器,所以代理对象可以知道原对象的具体类名、方法、参数,本示例在调用方法前就输出了这些。

实际应用中可能会针对类名而有所选择。比如接口中有好多个类,你可以选择性的对特定的类、方法、参数进行处理

比如 if(proxied instanceof RealObject) {} 或者 if(method.getName.equals("doSomething")) {}

总结

以上がJavaのRTTIとリフレクション機構の詳しい説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート