C++自身并没有提供像Java这样完备的反射机制,只是提供了非常简单的动态类型信息,如type_info和typeid。然而在一些C++的第三方框架类库中提供了类似的功能,如MFC、QT。其中MFC是通过宏的方式实现,
QT是通过自己的预编译实现。在目前的主流开发语言中,也只有C#提供的反射机制可以和Java的相提并论。
1. Java的反射机制主要表现为四点:
1) 在运行中分析类的能力;
2) 在运行中查看对象;
3) 实现数组的操作代码;
4) 利用Method对象,这个对象很像C++中的函数指针。
注:Java的基于反射的应用主要用于一些工具类库的开发,在实际的应用程序开发中应用的场景较少。
2. 获取对象的名称(字符串形式) vs 通过对象的名称(字符串形式)创建对象实例,见如下代码:
public static void main(String args[]) {
//1. 通过对象获取其字符串表示的名称
Date d = new Date();
//or Class<? extends Date> c1 = d.class;
Class<? extends Date> c1 = d.getClass();
String className = c1.getName();
//2. 通过字符串形式的名称创建类实例。
className = "java.util." + className;
try {
Class c2 = Class.forName(className);
//这里用到的newInstance用于创建c2所表示的对象实例,但是必须要求待创建的类实例
//具有缺省构造函数(无参数),很明显newInstance调用并未传入任何参数用于构造对象。
Date d2 = (Date)c2.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
如果需要通过调用带有参数的构造函数来创建对象实例,需要使用java.lang.reflect.Constructor对象来完成,见如下代码:
public static void main(String args[]) {
String className = "java.util.Date";
try {
Class c2 = Class.forName(className);
//找到只接受一个long类型参数的构造器
Constructor cc = c2.getConstructor(long.class);
long ll = 45L;
//将该Constructor期望的指定类型(long)的参数实例传入并构造Date对象。
Date dd = (Date)cc.newInstance(ll);
System.out.println("Date.toString = " + dd);
} catch (Exception e) {
e.printStackTrace();
}
}
3. 遍历一个未知类型的所有域、构造方法和域方法,见如下函数原型:
Field[] getFields(); 返回指定对象域字段数组,主要包含该类及其超类的所有公有(public)域。
Field[] getDeclaredFields();返回指定对象域字段数组,主要包含该类自身的所有域,包括private等。
Method[] getMethods(); 返回指定对象域方法数组,主要包含该类及其超类的所有公有(public)域方法。
Method[] getDeclaredMethods();返回指定对象域方法数组,主要包含该类自身的所有域方法,包括private等。
Constructor[] getConstructors(); 返回指定对象构造函数数组,主要包含该类所有公有(public)域构造器。
Constructor[] getDeclaredConstructors();返回指定对象构造函数数组,主要包含该类所有域构造器。
int getModifiers(); 返回一个用于描述构造器、方法或域的修饰符的整型数值,使用Modifier类中的静态方法可以协助分析这个返回值。
String getName(); 返回一个用于描述构造器、方法和域名的字符串。
Class[] getParameterTypes(); 返回一个用于描述参数类型的Class对象数组。
Class[] getReturnType(); 返回一个用于描述返回值类型的Class对象。
4. 通过反射编写泛型数组代码,见如下代码比较:
static Object[] badArrayGrow(Object[] a) {
int newLength = a.length * 11 / 10 + 10;
//该对象数组的在创建时是基于Object的,所以返回后,
//再装回其他类型数组时将会抛出ClassCastException的异常。
Object[] newArray = new Object[newLength];
System.arraycopy(a,0,newArray,0,a.length);
return newArray;
}
static Object goodArrayGrow(Object a) {//这里的参数务必为Object,而不是Object[]
Class c1 = a.getClass();
if (!c1.isArray())
return null;
//这里用于获取数组成员的类型
Class componentType = c1.getComponentType();
//获取数组的长度。
int length = Array.getLength(a);
int newLength = length * 11 / 10 + 10;
//通过数组成员的类型和新的长度值来创建一个和参数类型相同的数组,
//并增加他的空间,最后再返回。
Object newArray = Array.newInstance(componentType,newLength);
System.arraycopy(a,0,newArray,0,length);
return newArray;
}
5. 在运行时使用反射的对象或动态调用反射之后的方法。
1) 获取域字段和设置域字段: <pre name="code" class="java">public void testField() {
Employee harry = new Employee("Harry Hacker",35000,10);
Class c1 = harry.getClass();
Field f = c1.getDeclaredField("name");
//由于name字段有可能是Employee类的私有域字段,如果直接调用会致使JVM
//抛出安全异常,为了避免该异常的发生,需要调用下面的语句来得以保证。
f.setAccessible(true);
Object v = f.get(harry);
System.out.println(v);
}
2) 通过Method的invoke函数动态调用反射后的方法:
该方式有些类似于C#的委托(delegate)和C++的函数指针。
public int add(int param1, int param2) {
return param1 + param2;
}
public static void main(String[] args) throws Exception {
Class classType = MyTest.class;
Object myTest = classType.newInstance();
Method addMethod = classType.getMethod("add",int.class,int.class);
//如果add为静态方法,这里的第一个参数传null
Object result = addMethod.invoke(myTest, 100,200);
System.out.println(result);
}