Maison  >  Article  >  Java  >  Explication détaillée du RTTI et du mécanisme de réflexion en Java

Explication détaillée du RTTI et du mécanisme de réflexion en Java

黄舟
黄舟original
2017-09-20 09:59:531272parcourir

Cet article concerne principalement l'analyse du code du RTTI et du mécanisme de réflexion de Java. Tout en introduisant l'identification du type d'exécution, il vous montre également ses exemples et quand le mécanisme de réflexion sera utilisé. Il est riche en contenu et en besoins. Les amis peuvent s'y référer.

RTTI, c'est-à-dire identification du type d'exécution, identification du type d'exécution. L'identification du type d'exécution est un mécanisme très utile en Java. Pendant l'exécution de Java, RTTI conserve les informations relatives aux classes. RTTI peut identifier automatiquement chaque type connu au moment de la compilation au moment de l'exécution.

Souvent, il est nécessaire d'effectuer une transformation vers le haut. Par exemple, la classe Base dérive la classe Derived, mais la méthode existante n'a besoin que de l'objet Base comme paramètre, et ce qui est réellement transmis est celui-ci. une référence à sa classe dérivée. RTTI joue alors un rôle à ce moment-là. Par exemple, RTTI peut identifier que la classe Derive est une classe dérivée de Base, afin qu'elle puisse être transformée vers le haut en Derived. De même, lors de l'utilisation d'interfaces comme paramètres, la transformation ascendante est plus couramment utilisée, et RTTI peut déterminer si une transformation ascendante peut être effectuée à ce moment-là.

Ces informations de type sont complétées via l'objet spécial de l'objet Class (java.lang.Class), qui contient des informations relatives à la classe. Chaque fois qu'une classe est écrite et compilée, un fichier .class est généré, qui contient l'objet Class. La machine virtuelle Java (JVM) exécutant ce programme utilisera un sous-système appelé chargeur de classe (Class Loader). Le chargeur de classe ne charge pas tous les objets Class avant l'exécution du programme. S'ils n'ont pas été chargés, le chargeur de classe par défaut recherchera le fichier .class en fonction du nom de la classe (par exemple, un chargeur de classe supplémentaire peut rechercher le mot). dans la base de données), le bytecode de cette classe est vérifié lors de son chargement pour s'assurer qu'il n'a pas été corrompu et ne contient pas de mauvais code Java. C'est également l'un des mécanismes de sécurité de type Java. Une fois l'objet Class d'une certaine classe chargé en mémoire, tous les objets de cette classe peuvent être créés.


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]
  */
}

Dans le code ci-dessus, la méthode forName est une méthode statique, et le paramètre est le nom de la classe , utilisé pour déterminer si elle existe. Cette classe, si elle est trouvée, renvoie une référence Class, sinon une exception ClassNotFoundException sera levée.

Si la classe n'est pas dans le dossier par défaut, mais dans un certain package, le nom du package précédent doit être inclus, tel que typeinfo.Dérivé ici.

Vous pouvez utiliser la méthode getSuperclass pour renvoyer l'objet Class correspondant à la classe de base. Utilisez la méthode newInstance pour créer un objet instance selon la construction par défaut et lancez-le lorsqu'il ne peut pas être instancié et inaccessible. InstantiationException et IllegalAccessException seront levées.

Java fournit également un moyen de générer une référence à un objet Class, c'est-à-dire un littéral de classe. Pour le programme ci-dessus, up équivaut à Base.class.

Pour les classes wrapper de types de données de base, char.class est équivalent à Character.TYPE et int.class est équivalent à Integer.TYPE. Le reste ab.class est équivalent à Ab.TYPE. (Par exemple, void.class est équivalent à Void.TYP). De plus, Java SE5 démarre int.class et Integer.class, c'est la même chose.

Référence de classe généralisée, voir le code ci-dessous


    Class intClass = int.class;
    Class<Integer> genericIntClass = int.class;
    genericIntClass = Integer.class; // 等价
    intClass = double.class; // ok
    // genericIntClass = double.class; // Illegal!

La référence à l'objet Classc0f559cc8d56b43654fcbe4aa9df7b4a il ne peut pas pointer la référence vers double.class. Afin d'assouplir la restriction, vous pouvez utiliser le caractère générique ?, c'est-à-dire Class6b3d0130bba23ae47fe2b8e8cddf0195. L'effet est le même que celui de Class, mais le code est plus élégant. L'utilisation de Class6b3d0130bba23ae47fe2b8e8cddf0195 utiliser une référence de classe non spécifique par accident ou par négligence. Dans le même temps, les classes héritées peuvent être restreintes, comme le montre l'exemple suivant : Des erreurs de type peuvent être trouvées.


Pour résumer, les formulaires RTTI que nous connaissons comprennent :

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
  } 
}

1. Conversion de type traditionnelle, RTTI garantit l'exactitude de la conversion de type en cas d'exécution incorrecte. la conversion de type lancera une ClassCastException ;

2. L'objet Class représentant le type d'objet peut être obtenu en interrogeant l'objet Class (c'est-à-dire en appelant la méthode de la classe Class) pour obtenir le runtime. informations sur les exigences.

La conversion de type classique en C++ n'utilise pas RTTI. Pour plus de détails, consultez la section RTTI de C++. (En passant, lorsque j'apprenais le C++, j'ai juste jeté un coup d'œil au chapitre RTTI avec désinvolture. Maintenant, je me souviens que Dynamic_cast et d'autres éléments ont été ajoutés spécifiquement pour la sécurité des types. C++ peut fournir une sélectivité en termes de sécurité, tout comme Java. StringBuilder et StringBuffer, la sécurité et l'efficacité ne peuvent pas être obtenues en même temps ? Java est plus obligatoire en termes de sécurité de type, tout comme l'expression x = 1 ne peut pas être implicitement convertie en type booléen).

Il existe une troisième forme de RTTI en Java, qui est le mot-clé instanceof, qui renvoie une
valeur booléenne

pour indiquer si l'objet est un exemple d'un type spécifique. le code suivant.


Utilisez instanceof pour déterminer certains types. Par exemple, la classe de base Shape dérive diverses classes (Circle, Rectangle, etc.). utilisé pour tous les cercles, et les paramètres d'entrée sont un ensemble d'objets Shape. À ce stade, vous pouvez utiliser instandof pour déterminer si l'objet Shape est un objet 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, Class6b3d0130bba23ae47fe2b8e8cddf0195[] 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")) {}

总结

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn