Thinking in Java RTTI和反射机制

来源:互联网 发布:用c语言求学生的成绩 编辑:程序博客网 时间:2024/04/28 23:19

意义

编程思想中的解释:运行时类型信息使得你可以在程序运行时发现和使用类型信息。并不是所有的Class都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程),这就是RTTI(Runtime Type Information,运行时类型信息)。在Java中,在运行时识别对象和类的信息主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“发射”机制,它允许我们在运行时发现和使用类的信息。


Class对象

要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。

多态(polymorphism)是基于RTTI实现的。RTTI的功能主要是由Class类实现的。Class类是"类的类"(class of classes)。如果说类是对象的抽象和集合的话,那么Class类就是对类的抽象和集合。每一个Class类的对象代表一个其他的类。比如下面的程序中,Class类的对象c1代表了Human类,c2代表了Woman类。

public class Test{    public static void main(String[] args)    {        Human aPerson = new Human();        Class c1      = aPerson.getClass();        System.out.println(c1.getName());        Human anotherPerson = new Woman();        Class c2      = anotherPerson.getClass();        System.out.println(c2.getName());      }}class Human{        /**     * accessor     */    public int getHeight()    {       return this.height;    }    /**     * mutator     */    public void growHeight(int h)    {        this.height = this.height + h;    }private int height; }class Woman extends Human{    /**     * new method     */    public Human giveBirth()    {        System.out.println("Give birth");        return (new Human());    }}

当我们调用对象的getClass()方法时,就得到对应Class对象的引用。

在c2中,即使我们将Women对象的引用向上转换为Human对象的引用,对象所指向的Class类对象依然是Woman。

Java中每个对象都有相应的Class类对象,因此,我们随时能通过Class对象知道某个对象“真正”所属的类。无论我们对引用进行怎样的类型转换,对象本身所对应的Class对象都是同一个。当我们通过某个引用调用方法时,Java总能找到正确的Class类中所定义的方法,并执行该Class类中的代码。由于Class对象的存在,Java不会因为类型的向上转换而迷失。这就是多态的原理。

除了getClass()方法外,我们还有其他方式调用Class类的对象。

public class Test{    public static void main(String[] args)    {        Class c3      = Class.forName("Human");        System.out.println(c1.getName());        Class c4      = Woman.class//类字面常量        System.out.println(c2.getName());      }}

上面显示了两种方式:

  • forName()方法接收一个字符串作为参数,该字符串是类的名字。这将返回相应的Class类对象。(这种访问方法会导致Class类的初始化)
  • Woman.class方法(类字面常量)是直接调用类的class成员。这将返回相应的Class类对象。(这种仅使用.class语法来获得对类的引用不会引发Class类的初始化)

Class类的加载

当Java创建某个类的对象,比如Human类对象时,Java会检查内存中是否有相应的Class对象。

如果内存中没有相应的Class对象,那么Java会在.class文件中寻找Human类的定义,并加载Human类的Class对象。

在Class对象加载成功后,其他Human对象的创建和相关操作都将参照该Class对象

instanceof与Class的等价性

在查询类型信息时,以instanceof的形式(即以instanceof的形式或isInstance()的形式,它们产生相同的结果)与直接比较Class对象有一个很重要的区别,下面的例子展示了这种差别:

//: typeinfo/FamilyVsExactType.java// The difference between instanceof and classpackage typeinfo;import static net.mindview.util.Print.*;class Base {}class Derived extends Base {}public class FamilyVsExactType {  static void test(Object x) {    print("Testing x of type " + x.getClass());    print("x instanceof Base " + (x instanceof Base));    print("x instanceof Derived "+ (x instanceof Derived));    print("Base.isInstance(x) "+ Base.class.isInstance(x));    print("Derived.isInstance(x) " +      Derived.class.isInstance(x));    print("x.getClass() == Base.class " +      (x.getClass() == Base.class));    print("x.getClass() == Derived.class " +      (x.getClass() == Derived.class));    print("x.getClass().equals(Base.class)) "+      (x.getClass().equals(Base.class)));    print("x.getClass().equals(Derived.class)) " +      (x.getClass().equals(Derived.class)));  }  public static void main(String[] args) {    test(new Base());    test(new Derived());  }} /* Output:Testing x of type class typeinfo.Basex instanceof Base truex instanceof Derived falseBase.isInstance(x) trueDerived.isInstance(x) falsex.getClass() == Base.class truex.getClass() == Derived.class falsex.getClass().equals(Base.class)) truex.getClass().equals(Derived.class)) falseTesting x of type class typeinfo.Derivedx instanceof Base truex instanceof Derived trueBase.isInstance(x) trueDerived.isInstance(x) truex.getClass() == Base.class falsex.getClass() == Derived.class truex.getClass().equals(Base.class)) falsex.getClass().equals(Derived.class)) true*///:~

test()方法使用了两种形式的instanceof作为参数来执行类型检查。然后获取Class引用,并用==和equals来检查Class对象是否相等。使人放心的是,instanceof和isInstance()生成的结果完全一样,equals()和==也一样。但是这两组测试得出的结论却不相同。instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。

新的转型语法

Java SE5还添加了用于Class引用的转型语法,即cast()方法:

//: typeinfo/ClassCasts.javaclass Building {}class House extends Building {}public class ClassCasts {  public static void main(String[] args) {    Building b = new House();    Class<House> houseType = House.class;    House h = houseType.cast(b);    h = (House)b; // ... or just do this.  }} ///:~
cast()方法接受参数对象,并将其转型为Class引用的类型。实现功能与main()中最后一行相同。

RTTI

严格的说,反射也是一种形式的RTTI,不过,一般的文档资料中把RTTI和反射分开,因为一般的,大家认为RTTI指的是传统的RTTI,通过继承和多态来实现,在运行时通过调用超类的方法来实现具体的功能(超类会自动实例化为子类,或使用instanceof)。

迄今为止,我们已知的RTTI形式有三种:

1)向上转型或向下转型(upcasting and downcasting),在java中,向下转型(父类转成子类)需要强制类型转换

2)代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。

3)instanceof或isInstance(),例如下面的例子。

if(x instanceof Dog)   ((Dog)x).bark();
在将x转型成一个Dog前,上面的if语句会检查对象x是否从属于Dog类。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用instanceof是非常重要的,否则会得到一个ClassCastException异常。
RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件,因此编译期class文件是必须;而对于发射机制来说,.class文件在编译期是不可获取的,所以是在运行时打开和检查.class文件。

public void rtti(Object obj){    Toy toy = Toy(obj);    // Toy toy = Class.forName("myblog.rtti.Toy")    // obj instanceof Toy}

注意其中的obj虽然是被转型了,但在编译期,就需要知道要转成的类型Toy,也就是需要Toy的.class文件。

相对的,反射完全在运行时在通过Class类来确定类型,不需要提前加载Toy的.class文件。

反射

那到底什么是反射(Reflection)呢?反射有时候也被称为内省(Introspection),事实上,反射,就是一种内省的方式,Java不允许在运行时改变程序结构或类型变量的结构,但它允许在运行时去探知、加载、调用在编译期完全未知的class,可以在运行时加载该class,生成实例对象(instance object),调用method,或对field赋值。这种类似于“看透”了class的特性被称为反射(Reflection),我们可以将反射直接理解为:可以看到自己在水中的倒影,这种操作与直接操作源代码效果相同,但灵活性高得多。

Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field,Method以及Constructor类(每个类都实现了Member接口)。这种类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。两外,还可以调用getFields(),getMethods()和getConstructors()等很便利的方法,以返回表示字段,方法以及构造器的对象的数组(在JDK文档中,通过查找class类可了解更多相关资料)。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。

//: typeinfo/ShowMethods.java// Using reflection to show all the methods of a class,// even if the methods are defined in the base class.// {Args: ShowMethods}import java.lang.reflect.*;import java.util.regex.*;import static net.mindview.util.Print.*;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 'word'";  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]);      Method[] methods = c.getMethods();      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(""));        lines = methods.length + ctors.length;      } else {        for(Method method : methods)          if(method.toString().indexOf(args[1]) != -1) {            print(              p.matcher(method.toString()).replaceAll(""));            lines++;          }        for(Constructor ctor : ctors)          if(ctor.toString().indexOf(args[1]) != -1) {            print(p.matcher(              ctor.toString()).replaceAll(""));            lines++;          }      }    } catch(ClassNotFoundException e) {      print("No such class: " + e);    }  }} /* Output:public static void main(String[])public native int hashCode()public final native Class getClass()public final void wait(long,int) throws InterruptedExceptionpublic final void wait() throws InterruptedExceptionpublic final native void wait(long) throws InterruptedExceptionpublic boolean equals(Object)public String toString()public final native void notify()public final native void notifyAll()public ShowMethods()*///:~
上述例子中Class.forName()生成的结果在编译时是不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的。上面的输出是从下面的命令行产生的:

java ShowMethods ShowMethods

其中很多未定义的方法都是从基类继承下来的。


反射的性能

反射机制给予Java开发很大的灵活性,但反射机制本身也有缺点,代表性的缺陷就是反射的性能,一般来说,通过反射调用方法的效率比直接调用的效率要至少慢一倍以上。

关于性能的问题,可以参考这篇博客http://blog.csdn.net/l_serein/article/details/6219897

反射与设计模式

反射的一个很重要的作用,就是在设计模式中的应用,包括在工厂模式和代理模式中的应用。关于这一方面,我会在后续的文章中介绍,有兴趣的朋友也先可以参考这篇文章http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html中关于动态代理模式实现方法的文章。

参考资料

Java编程思想  第14章

Java系列笔记  RTTI与反射机制


0 0
原创粉丝点击