现在的位置: 首页 > 综合 > 正文

Java RTTI 运行时类型识别 和反射

2013年09月13日 ⁄ 综合 ⁄ 共 8508字 ⁄ 字号 评论关闭

(run-time type identification ,RTTI)

Class对象是RTTI的核心,每个类都有一个class对象。每当编写并且编译一个新类,就会产生一个Class对象(被保存在同名的.class文件当中)

    
Class.forName("classname"),如果对象没有加载就加载对象(这将会触发类的静态初始化)
      Class.newInstance()用来产生一个对象。如
      Class m = Class.forName("classname");//1
      Object o = m.newInstance();//2
      java也提供" 类字面常量" 的机制生成对象的引用。像这样:
      A.class
     对于基本类型,boolean.class === Boolean.TYPE , char.class ===Character.TYP
     void.class ===Void.TYPE,等等
     那么也可以用Class m = char.class;  //或者 Class m = <aclass><aclass>.class
                             Object  o = m.newInstance();
                             ((Char)o).××

instanceof 关键字用于检查对象是不是某个特定类型的实例。这用于类型转换前做检测。如:
      if  ( x   instanceof Dog )
              ((Dog)x).bark();
      </aclass>除了 instanceof 关键字以外,还可以使用 Class.isInstance() 方法,两者功能相同。
<aclass>
instanceof的替代方案是: x.getClass == Y.class 或者x.getClass.equals( Y.class)
Class对象的getInterfaces()获得接口,getSurperClass 或者获得超类。

下面看一个thinking in java中的例子:

  1.    
    1

    //: ToyTest.java  
      
  2.    2

    // Testing class Class  
      
  3.    3

    interface
     HasBatteries {}    
  4.    4

    interface
     Waterproof {}    
  5.    5

    interface
     ShootsThings {}    
  6.    6

    class
     Toy {    
  7.    7

    // Comment out the following default  
      
  8.    8

    // constructor to see  
      
  9.    9

    // NoSuchMethodError from (*1*)  
      
  10.   10
    . Toy() {}    
  11.   11
    . Toy(
    int
     i) {}    
  12.   12
    . }    
  13.   13

    class
     FancyToy 
    extends
     Toy    
  14.   14

    implements
     HasBatteries,    
  15.   15
    . Waterproof, ShootsThings {    
  16.   16
    . FancyToy() { 
    super
    (
    1
    ); }    
  17.   17
    . }    
  18.   18

    public
     
    class
     ToyTest {    
  19.   19

    public
     
    static
     
    void
     main(String[] args) {    
  20.   20
    . Class c = 
    null
    ;    
  21.   21

    try
     {    
  22.   22
    . c = Class.forName(
    "thinking.java.example_c11.FancyToy"
    );    
  23. //此处的FancyToy类名要把包名包含全,不然编译器找不到
      
  24.   23
    . } 
    catch
    (ClassNotFoundException e) {}    
  25.   24
    . printInfo(c);    
  26.   25
    . Class[] faces = c.getInterfaces();    
  27.   26

    for
    (
    int
     i = 
    0
    ; i < faces.length; i++)    
  28.   27
    . printInfo(faces[i]);    
  29.   28
    . Class cy = c.getSuperclass();    
  30.   29
    . Object o = 
    null
    ;    
  31.   30

    try
     {    
  32.   31

    // Requires default constructor:  
      
  33.   32
    . o = cy.newInstance(); 
    // (*1*)  
      
  34.   33
    . } 
    catch
    (InstantiationException e) {}    
  35.   34

    catch
    (IllegalAccessException e) {}    
  36.   35
    . printInfo(o.getClass());    
  37.   36
    . }    
  38.   37

    static
     
    void
     printInfo(Class cc) {    
  39.   38
    . System.out.println(    
  40.   39

    "Class name: "
     + cc.getName() +    
  41.   40

    " is interface? ["
     +    
  42.   41
    . cc.isInterface() + 
    "]"
    );    
  43.   42
    . }    
  44.   43
    . } 
    ///  
      
  45.     

 从中可以看出,class FancyToy 相当复杂,因为它从Toy 中继承,并实现了HasBatteries,Waterproof 以 
  及ShootsThings 的接口。在main()中创建了一个Class 句柄,并用位于相应try 块内的forName()初始化成 
  FancyToy。 
  Class.getInterfaces 方法会返回Class 对象的一个数组,用于表示包含在Class 对象内的接口。 
  若有一个Class 对象,也可以用getSuperclass()查询该对象的直接基础类是什么。当然,这种做会返回一 
   个Class 句柄,可用它作进一步的查询。这意味着在运行期的时候,完全有机会调查到对象的完整层次结构。 
   若从表面看,Class 的newInstance()方法似乎是克隆(clone())一个对象的另一种手段。但两者是有区别 
   的。利用newInstance(),我们可在没有现成对象供“克隆”的情况下新建一个对象。就象上面的程序演示 
   的那样,当时没有Toy 对象,只有cy——即y 的Class 对象的一个句柄。利用它可以实现“虚拟构建器”。 
   换言之,我们表达:“尽管我不知道你的准确类型是什么,但请你无论如何都正确地创建自己。”在上述例 
   子中,cy 只是一个Class 句柄,编译期间并不知道进一步的类型信息。一旦新建了一个实例后,可以得到 
   Object 句柄。但那个句柄指向一个Toy 对象。当然,如果要将除Object 能够接收的其他任何消息发出去, 
   首先必须进行一些调查研究,再进行造型。除此以外,用newInstance()创建的类必须有一个默认构建器。 
   没有办法用newInstance()创建拥有非默认构建器的对象,所以在Java 1.0 中可能存在一些限制。然而, 
   Java 1.1 的“反射”API(下一节讨论)却允许我们动态地使用类里的任何构建器。 
   程序中的最后一个方法是printInfo(),它取得一个Class 句柄,通过getName()获得它的名字,并用 
   interface()调查它是不是一个接口。 
   该程序的输出如下: 
   Class name: FancyToy is interface? [false] 
   Class name: HasBatteries is interface? [true] 
   Class name: Waterproof is interface? [true] 
   Class name: ShootsThings is interface? [true] 
   Class name: Toy is interface? [false]

反射

        RTTI 会帮助我们调查出对象的准确类型,但却有一个限制:类型必须是在编译期间已知的,否则就不能用RTTI 调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道RTTI 要处理的所有类。

       假若得到的是一个不在自己程序空间内的对象的句柄,假设我们从磁盘或者网络获得一系列字节,而且被告知那些字节代表一个类。由于编译器在编译代码时并不知道那个类的情况,所以怎样才能顺利地使用这个类呢?

       “反射”提供了一种特殊的机制,可以侦测可用的方法,并产生方法名,在Java 1.1 中,Class
类(本章前面已有详细论述)得到了扩展,可以支持“反射”的概念。针对Field,Method 以及Constructor
类(每个都实现了Memberinterface——成员接口),它们都新增了一个库:java.lang.reflect。这些类型的对象都是JVM
在运行期创建的,用于代表未知类里对应的成员。这样便可用构建器创建新对象,用get()和set()方法读取和修改与Field
对象关联的字段,以及用invoke()方法调用与Method
对象关联的方法。此外,我们可调用方法getFields(),getMethods(),getConstructors(),分别返回用于表示字段、
方法以及构建器的对象数组(在联机文档中,还可找到与Class
类有关的更多的资料)。因此,匿名对象的类信息可在运行期被完整的揭露出来,而在编译期间不需要知道任何东西。大家要认识的很重要的一点是“反射”并没有
什么神奇的地方。通过“反射”同一个未知类型的对象打交道时,JVM 只是简单地检查那个对象,并调查它从属于哪个特定的类(就象以前的RTTI
那样)。但在这之后,在我们做其他任何事情之前,Class 对象必须载入。因此,用于那种特定类型的.class 文件必须能由JVM
调用(要么在本地机器内,要么可以通过网络取得)。所以RTTI 和“反射”之间唯一的区别就是对RTTI
来说,编译器会在编译期打开和检查.class 文件。换句话说,我们可以用“普通”方式调用一个对象的所有方法;但对“反射”来说,.class
文件在编译期间是不可使用的,而是由运行期环境打开和检查。

  1. //: ShowMethods.java
      
  2. // Using Java 1.1 reflection to show all the
      
  3. // methods of a class, even if the methods are
      
  4. // defined in the base class.
      
  5. import
     java.lang.reflect.*;  
  6. public
     
    class
     ShowMethods {  
  7. static
     
    final
     String usage =  
  8. "usage: /n"
     +  
  9. "ShowMethods qualified.class.name/n"
     +  
  10. "To show all methods in class or: /n"
     +  
  11. "ShowMethods qualified.class.name word/n"
     +  
  12. "To search for methods involving 'word'"
    ;  
  13. public
     
    static
     
    void
     main(String[] args) {  
  14. if
    (args.length < 
    1
    ) {  
  15. System.out.println(usage);  
  16. System.exit(0
    );  
  17. }  
  18. try
     {  
  19. Class c = Class.forName(args[0
    ]);  
  20. Method[] m = c.getMethods();  
  21. Constructor[] ctor = c.getConstructors();  
  22. if
    (args.length == 
    1
    ) {  
  23. for
     (
    int
     i = 
    0
    ; i < m.length; i++)  
  24. System.out.println(m[i].toString());  
  25. for
     (
    int
     i = 
    0
    ; i < ctor.length; i++)  
  26. System.out.println(ctor[i].toString());  
  27. }  
  28. else
     {  
  29. for
     (
    int
     i = 
    0
    ; i < m.length; i++)  
  30. if
    (m[i].toString()  
  31. .indexOf(args[1
    ])!= -
    1
    )  
  32. System.out.println(m[i].toString());  
  33. for
     (
    int
     i = 
    0
    ; i < ctor.length; i++)  
  34. if
    (ctor[i].toString()  
  35. .indexOf(args[1
    ])!= -
    1
    )  
  36. System.out.println(ctor[i].toString());  
  37. }  
  38. catch
     (ClassNotFoundException e) {  
  39. System.out.println("No such class: "
     + e);  
  40. }  
  41. }  
  42. ///
      

Class 方法getMethods()和getConstructors()可以分别返回Method 和Constructor 的一个数组。每个类都
提供了进一步的方法,可解析出它们所代表的方法的名字、参数以及返回值。但也可以象这样一样只使用
toString(),生成一个含有完整方法签名的字串。代码剩余的部分只是用于提取命令行信息,判断特定的签
名是否与我们的目标字串相符(使用indexOf()),并打印出结果。
这里便用到了“反射”技术,因为由Class.forName()产生的结果不能在编译期间获知,所以所有方法签名
信息都会在运行期间提取。若研究一下联机文档中关于“反射”(Reflection)的那部分文字,就会发现它
已提供了足够多的支持,可对一个编译期完全未知的对象进行实际的设置以及发出方法调用。同样地,这也
属于几乎完全不用我们操心的一个步骤——Java 自己会利用这种支持,所以程序设计环境能够控制Java
Beans——但它无论如何都是非常有趣的。

反射方法调用:

这样写(假设方法的参数是String[],如main(String[]   args)):
Class   yourClass   =   Class.forName( "YourClass ");//假设你要动态加载的类为YourClass

Class[]   parameterTypes   =   new   Class[1];//这里你要调用的方法只有一个参数

parameterTypes[0]   =   String[].class;//这个参数的类型是String型的/////应该是String[]

Method   method   =   yourClass.getMethod( "main ",   parameterTypes);//这里假设你的类为YourClass,而要调用的方法是main

Object[]   args   =   new   Object[1];//假设你要传入两个参数////应该是一个
String[]   argments   =   new   String[2];//假设你要传入两个参数
argments[0]   =   "OK ";
argments[1]   =   "NO ";
args[0]   =   argments;

method.invoke(yourClass.newInstance(),   args);//调用方法

【上篇】
【下篇】

抱歉!评论已关闭.