Java RTTI与反射

来源:互联网 发布:2014新疆网络管制 编辑:程序博客网 时间:2024/05/23 13:21

Java提供了在运行时发现和使用类型信息的能力。

RTTI:运行时类型识别

运行时类型识别(RTTI)可以让你在程序运行时发现和使用类型信息,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个类型标识跟踪每个对象所属的类,虚拟机利用运行时类型信息选择相应的方法执行,保存这些信息的类就是Class类,Class对象是在运行时加载到JVM中的。
Java中的任何类型都具有一个类字面常量,例如int.class,它在编译时就会受到检查,可用于类、接口、数组和基本数据类型。

为了使用类,所做的工作有:
- 加载:由类加载器执行,将查找.class文件,并创建一个Class对象;
- 链接:验证类中的字节码,为静态域分配存储空间,解析此类创建时对其他类的引用;
- 初始化:执行静态初始化器和静态初始化块。

注意:
- 当使用类字面常量来创建对Class对象的引用时,不会初始化该Class对象;
- 如果一个static final域是编译期常量,则此域不用对类进行初始化就能被读取;
- 如果一个static域不是final的,则在访问它时,必须先进行链接和初始化。

反射:运行时的类信息

Class类与java.lang.reflect包一起对反射提供了支持。java.lang.reflect包中的FieldMethodConstructor类分别用于描述类的域、方法和构造器,这些类型的对象由JVM在运行时创建,用来表示未知类中的成员。
通过Field类可以获得数据域的类型、数据某一时刻的状态(值)和数据的修饰符;Method类中包括获得方法参数、方法返回值和方法修饰符的方法;而Constructor类提供了获取构造器参数和构造器修饰符的方法,另外还可以通过Constructor类在运行时新建对象实例(调用相应的构造器方法)。
java.lang.reflect包提供了一个Modifier类,此类可以用来分析FieldMethodConstructor类中获取的修饰符。
通过Class对象的getFieldsgetMethodsgetConstructors方法可以获得此Class类中的public域、方法和构造器,其中包括父类的public成员;通过Class对象的getDeclareFieldsgetDeclareMethodsgetDeclareConstructors方法可以获得此Class类中声明的全部域、方法和构造器,包括private和protected成员,但不包括父类的成员。

用反射获取类信息

下面设计了一个在运行时查看类结构的类ReflectUtil,它能显示一个类的继承层次:

public class ReflectUtil {    public static Class<?> analyseClass(String className){        try {            // 加载类信息            Class<?> cl=Class.forName(className);            // 打印类头 - public class MyClass2 extends MyClass1            Class<?> supercl=showClassHead(cl);            System.out.println("{");            // 打印数据域 - private String s;            showClassFields(cl);            // 打印构造器方法 - public MyClass2();            showClassConstructors(cl);            // 打印方法 - public void f();            showClassMethods(cl);            System.out.println("}");            return supercl;        } catch (ClassNotFoundException e) {            System.out.println("not find "+className);            return null;        }    }    public static Class<?> showClassHead(Class<?> cl){        // 打印修饰符        String modifier=Modifier.toString(cl.getModifiers());        if(modifier!=null && modifier.length()>0)            System.out.print(modifier+" ");        // 打印类名        System.out.print("class "+cl.getName()+" ");        // 打印父类        Class<?> supercl=cl.getSuperclass();        if(supercl!=null && supercl!=Object.class)            System.out.print("extends "+supercl.getName()+" ");        return supercl;    }    public static void showClassFields(Class<?> cl){        Field[] fields=cl.getDeclaredFields();        for(Field field : fields){            System.out.print("    ");            // 打印修饰符            String modifier=Modifier.toString(field.getModifiers());            if(modifier!=null && modifier.length()>0)                System.out.print(modifier+" ");            // 打印类型和变量名            System.out.println(field.getType().getName()+" "+field.getName()+";");        }    }    public static void showClassConstructors(Class<?> cl){        Constructor<?>[] constructors=cl.getDeclaredConstructors();        for(Constructor<?> constructor : constructors){            System.out.print("    ");            // 打印修饰符            String modifier=Modifier.toString(constructor.getModifiers());            if(modifier!=null && modifier.length()>0)                System.out.print(modifier+" ");            // 打印构造器方法名            System.out.print(cl.getName()+"(");            // 打印方法参数列表            Class<?>[] paramTypes=constructor.getParameterTypes();            if(paramTypes!=null && paramTypes.length>0){                int i;                for(i=0; i<paramTypes.length-1; i++)                    System.out.print(paramTypes[i].getName()+", ");                System.out.print(paramTypes[i].getName());            }            System.out.print(")");            // 打印异常声明列表            Class<?>[] exceptionTypes=constructor.getExceptionTypes();            if(exceptionTypes!=null && exceptionTypes.length>0){                System.out.print(" throws ");                int j;                for(j=0; j<exceptionTypes.length-1; j++)                    System.out.print(exceptionTypes[j].getName()+", ");                System.out.print(exceptionTypes[j].getName());            }            System.out.println(";");        }    }    public static void showClassMethods(Class<?> cl){        Method[] methods=cl.getDeclaredMethods();        for(Method method : methods){            System.out.print("    ");            // 打印修饰符            String modifier=Modifier.toString(method.getModifiers());            if(modifier!=null && modifier.length()>0)                System.out.print(modifier+" ");            // 打印方法返回类型            System.out.print(method.getReturnType().getName()+" ");            // 打印方法名            System.out.print(method.getName()+"(");            // 打印方法参数列表            Class<?>[] paramTypes=method.getParameterTypes();            if(paramTypes!=null && paramTypes.length>0){                int i;                for(i=0; i<paramTypes.length-1; i++)                    System.out.print(paramTypes[i].getName()+", ");                System.out.print(paramTypes[i].getName());            }            System.out.print(")");            // 打印异常声明列表            Class<?>[] exceptionTypes=method.getExceptionTypes();            if(exceptionTypes!=null && exceptionTypes.length>0){                System.out.print(" throws ");                int j;                for(j=0; j<exceptionTypes.length-1; j++)                    System.out.print(exceptionTypes[j].getName()+", ");                System.out.print(exceptionTypes[j].getName());            }            System.out.println(";");        }    }}

现在我们自己编写两个测试类ShapeCircle,让Circle继承自Shape,并且ShapeCircle类都在com.zzw.reflect包中:

// Shape类public class Shape {    private float width;    // 宽度    private float height;   // 高度    private float x;        // 横坐标    private float y;        // 纵坐标    private int color;      // 颜色    public Shape(float w, float h){        this(w, h, 0, 0, 0x000);    }    public Shape(float w, float h, float x, float y, int c){        width=w;        height=h;        this.x=x;        this.y=y;        color=c;    }    public float getWidth(){ return width; }    public float getHeight(){ return height; }    public float getX(){ return x; }    public float getY(){ return y; }    public int getColor(){ return color; }    public void setWidth(float w){ width=w; }    public void setHeight(float h){ height=h; }    public void setX(float x){ this.x=x; }    public void setY(float y){ this.y=y; }    public void setColor(int c){ color=c; }}// Circle类public class Circle extends Shape {    private float radius;   // 半径    public Circle(float r) {        super(r, r);        radius=r;    }    public float getRadius(){ return radius; }    public void setRadius(float r){ radius=r; }    public float getArea(){        return (float)(Math.PI*radius*radius);    }    private void f(int a, int b) throws Exception {        throw new Exception();    }    public int g(int c){        try {            f(c, c);        } catch (Exception e) {            e.printStackTrace();        }        return 0;    }}

下面是我们的测试代码:

        Class<?> supercl=null;        String className="com.zzw.reflect.Circle";        do{            supercl=ReflectUtil.analyseClass(className);            className=supercl.getName();            System.out.println("--------------------");        }while(supercl!=null && supercl!=Object.class);// output:public class com.zzw.reflect.Circle extends com.zzw.reflect.Shape {    private float radius;    public com.zzw.reflect.Circle(float);    public float getArea();    private void f(int, int) throws java.lang.Exception;    public void setRadius(float);    public int g(int);    public float getRadius();}--------------------public class com.zzw.reflect.Shape {    private float width;    private float height;    private float x;    private float y;    private int color;    public com.zzw.reflect.Shape(float, float);    public com.zzw.reflect.Shape(float, float, float, float, int);    public void setColor(int);    public int getColor();    public void setWidth(float);    public void setX(float);    public void setHeight(float);    public float getHeight();    public float getY();    public void setY(float);    public float getX();    public float getWidth();}--------------------

可以看到,我们成功的获得了Circle类的结构和继承层次,当然,如果你想知道其它类的信息,只需要简单改变一下analyseClass方法的参数就行了。

在运行时使用反射分析对象

现在我们已经知道如何通过反射来查看类结构,但你可能会说,直接看API和源代码不是更简单吗?这样说的确没错,但是反射不仅止于此,它还能在运行时查看和设置对象的状态,我们还是以Circle类为例说明。

        Circle circle=new Circle(1.0f);        try {            // 获取Circle对象的radius域            Field f=circle.getClass().getDeclaredField("radius");            // 由于radius是私有的,必须设置成可访问的            f.setAccessible(true);            float radius=(Float)f.get(circle);            // 或者 f.getFloat(circle);            System.out.println("radius="+radius);            // 设置Circle对象的radius域            f.set(circle, 10.0f);            // 或者 f.setFloat(circle, 10.0f);            System.out.println("radius="+circle.getRadius());        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }// output:radius=1.0radius=10.0

可以看到,通过反射,我们成功的获得和修改了Circle对象的radius域。此时你可能会说,我只要调用radiusgetset方法照样能行,为什么还要写这么长的代码?没错,这里可以直接用getset方法,但是,我们在编程中大部分使用的都是系统代码或者别人写的代码,如果他们没有提供getset方法呢?此时就只能使用反射,现在你知道反射的强大了吧。

调用任意方法,Java中的“函数指针”

Java中由于安全性的考虑没有提供指针类型,因此无法像C++那样任意的传递方法(函数)地址,相比于C++显得不是那么自由。反射机制在一定程度上弥补了这点,它允许使用者调用任意方法。
和访问/修改对象域类似,Method类提供了invoke方法来调用对象方法。这里我们先在Circle类中添加一些代码:

    public static void e(int a, int b){        System.out.println("this is Circle.e("+a+", "+b+")");    }    private void f(double a){        System.out.println("this is Circle instance's f("+a+")");    }

我们在Circle类中添加了一个公开的静态方法e和一个私有的重载方法f,下面我们将展示如何使用反射来调用这两个方法。

        Circle circle=new Circle(1.0f);        Method m=null;        try {            // 调用 Circle.e(int, int)            m=Circle.class.getDeclaredMethod("e", int.class, int.class);            m.invoke(null, 1, 2);            // 调用 circle.f(double)            m=circle.getClass().getDeclaredMethod("f", double.class);            m.setAccessible(true);            m.invoke(circle, 3);        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        }// output:this is Circle.e(1, 2)this is Circle instance's f(3.0)

注意到,对于静态方法和非静态方法的调用稍微有所区别。对于静态方法,可以使用Circle.class来获得Class对象,同时invoke方法的第一个参数设置成null;而非静态方法使用circle.getClass()来获得Class对象,同时invoke方法的第一个参数需要传入circle(运行时对象)。
由于一个类可以重载多个同名方法,因此在调用getDeclaredMethod方法时需要传入参数列表的类型序列。除此之外还要注意访问权限,可以看到对f方法的调用需要设置为可访问的。

在运行时创建对象

我们发现,Class类中有一个newInstance方法,它能调用相关类的默认构造方法来创建对象,但是对于那些没有默认构造方法或者想要调用其它构造方法的类,它就无能为力了。
Constructor类弥补了这一点,它能调用任意的构造器方法来创建对象,这里我们以创建一个Circle对象为例。

        try {            Constructor<Circle> c=Circle.class.getDeclaredConstructor(float.class);            Circle circle=c.newInstance(3.0f);            System.out.println("radius="+circle.getRadius());        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        }// output:radius=3.0

不出所料,我们利用Constructor对象成功创建了一个Circle对象。当然,这里我们调用的是Circle类的公开构造方法,如果想要调用一个私有构造方法,也要先设置访问权限哦。

反射:强大背后的隐患

通过一系列实例,相信你已经了解了Java的反射机制的强大能力,使用反射我们几乎可以做任何事情!但是,这并不意味着你能随意使用反射。
我们现在回头想想当初为什么要使用反射?访问/设置对象的私有/保护域?调用任意的对象方法?创建任意的对象实例?没错,通过反射这些我们都能做到。但是,Java作为一种OOP的语言,最明显的特点之一就是封装,而反射完全绕过了这个安全层!过度使用反射可能导致代码很快陷入混乱,而且它比起正常的操作速度也要慢很多。
那么,什么时候使用反射合适呢?我认为,在编写一般代码无法实现某个功能而反射能实现,或者实现某个功能的一般代码比起使用反射要繁琐很多时,可以考虑使用反射。

RTTI和反射

RTTI和反射都能在运行时发现和使用类型信息,不过反射相比于RTTI更加强大。另外,对于RTTI,编译器是在编译时打开和检查.class文件的;而反射在运行时打开和检查.class文件。

原创粉丝点击