Java RTTI与反射

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.0
radius=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文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值