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
包中的Field
、Method
和Constructor
类分别用于描述类的域、方法和构造器,这些类型的对象由JVM在运行时创建,用来表示未知类中的成员。
通过Field
类可以获得数据域的类型、数据某一时刻的状态(值)和数据的修饰符;Method
类中包括获得方法参数、方法返回值和方法修饰符的方法;而Constructor
类提供了获取构造器参数和构造器修饰符的方法,另外还可以通过Constructor
类在运行时新建对象实例(调用相应的构造器方法)。
java.lang.reflect
包提供了一个Modifier
类,此类可以用来分析Field
、Method
和Constructor
类中获取的修饰符。
通过Class对象的getFields
、getMethods
和getConstructors
方法可以获得此Class类中的public域、方法和构造器,其中包括父类的public成员;通过Class对象的getDeclareFields
、getDeclareMethods
和getDeclareConstructors
方法可以获得此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(";");
}
}
}
现在我们自己编写两个测试类Shape
和Circle
,让Circle
继承自Shape
,并且Shape
和Circle
类都在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
域。此时你可能会说,我只要调用radius
的get
和set
方法照样能行,为什么还要写这么长的代码?没错,这里可以直接用get
和set
方法,但是,我们在编程中大部分使用的都是系统代码或者别人写的代码,如果他们没有提供get
和set
方法呢?此时就只能使用反射,现在你知道反射的强大了吧。
调用任意方法,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
文件。