java的RTTI和反射机制

RTTI,即Run-Time Type Identification,运行时类型识别。RTTI能在运行时就可以自动识别每一个编译时已知的类型java

不少时候须要进行向上转型,好比Base类派生出Derived类,可是现有的方法只须要将Base对象做为参数,实际传入的则是其派生类的引用。那么RTTI就在此时起到了做用,好比经过RTTI能识别出Derive类是Base的派生类,这样就可以向上转型为Derived。相似的,在用接口做为参数时,向上转型更为经常使用,RTTI此时可以判断是否能够进行向上转型。数据库

而这些类型信息是经过Class对象(java.lang.Class)的特殊对象完成的,它包含跟类相关的信息。每当编写并编译一个类时就会产生一个.class文件,保存着Class对象,运行这个程序的Java虚拟机(JVM)将使用被称为类加载器(Class Loader)的子系统。而类加载器并不是在程序运行以前就加载全部的Class对象,若是还没有加载,默认的类加载器就会根据类名查找.class文件(例如,某个附加类加载器可能会在数据库中查找字节码),在这个类的字节码被加载时接受验证,以确保没有被破坏而且不包含不良Java代码。这也是Java中的类型安全机制之一。一旦某个类的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方法是静态方法,参数是类名,用来查找是否存在该类,若是找到则返回一个Class引用,不然会抛出ClassNotFoundException异常。设计模式

若是类不是在默认文件夹下,而是在某个包下,前面的包名须要带上,好比这里的typeinfo.Derived。安全

能够经过getSuperclass方法来返回基类对应的Class对象。使用newInstance方法能够按默认构造建立一个实例对象,在不能实例化和不能访问时分别抛出。会抛出InstantiationException和IllegalAccessException异常。网络

Java还提供了一种方法来生成对Class对象的引用,即类字面常量。对上述程序来讲,up等价于Base.class。eclipse

对于基本数据类型的包装类来讲,char.class等价于Character.TYPE,int.class等价于Integer.TYPE。其他的ab.class等价于Ab.TYPE。(好比void.class等价于Void.TYP)。另外,Java SE5开始int.class和Integer.class也是一回事。ide

 

泛化的Class引用,见下面代码函数

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

Class<Integer>对象的引用指定了Integer对象,因此不能将引用指向double.class。为了放松限制可使用通配符?,即Class<?>,效果跟Class是同样的,可是代码更为优雅,使用Class<?>表示你并不是是碰巧或疏忽才使用一个非具体的类引用。同时,能够限制继承的类,示例以下ui

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

向Class引用添加泛型语法的缘由仅仅是为了提供编译期类型检查,以便在编译时就能发现类型错误。

总结下来,咱们已知的RTTI形式包括:

一、传统的类型转换,由RTTI保证类型转换的正确性,若是执行一个错误的类型转换,就会抛出ClassCastException异常;

二、表明对象的类型的Class对象,经过查询Class对象(即调用Class类的方法)能够获取运行时所需的信息。

在C++中经典的类型转换并不使用RTTI,这点具体见C++的RTTI部分。(说句题外话,之前学C++时看到RTTI这章只是随便扫了眼,如今才记起来dynamic_cast什么的都是为了类型安全而特意添加的,C++在安全方面能够提供选择性,就像Java的StringBuilder和StringBuffer,安全和效率不可兼得?而Java在类型安全上则更为强制,就像表达式x = 1不能被隐式转型为boolean类型)。

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

利用instanceof能够判断某些类型,好比基类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 '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]);
            // 反射得到对象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")) {}
PS:我这个示例没有参数因此没有距离
 

参考:《Java编程思想》第四版,更多细节见书上第14章

相关文章
相关标签/搜索