反射机制使得人们可以通过在运行时查看域和方法,让人们编写出更具有通用性的程序。这种功能对于编写系统程序来说极其实用,但是通常不适用于编写应用程序。现在让我们来了解一下反射机制。我们把能够分析类能力的程序称为反射(reflective)。反射可以用于以下几个方面:
- 在运行中分析类的能力;
- 在运行中查看对象;
- 实现通用的数组操作代码;
- 利用Method对象,这个对象类似于C++中的函数指针;
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。通过这个信息可以跟踪着每个对象所属的类。虚拟机就是利用运行时类型信息选择相应的方法执行。在Java中,有一个专门的类来保存这些运行时的信息,这个类称为Class类。一个Class对象将表示一个特定类的属性。最常用的的Class方法是getName(),这个方法将返回类型的名字。可以通过三种方式获取一个Class类型的实例:
- Object类中的getClass()方法返回的就是一个Class类型的实例。例如:
Date date;Class cl = date.getClass( );
- 通过调用静态方法forName获得类名对应的Class对象。例如:
String className = “java.util.Date”;Class cl = Class.forName(className);
-
如果T是任意的Java类型,则T.class将代表匹配的类对象。例如:Class cl = Date.class;
当获取了一个类型的实例Class后,可以通过newInstance( )方法快速创建一个类的实例。例如:e.getClass( ).newInstance( );将创建一个与e具有相同类类型的实例。需要注意的是,newInstance方法调用的是默认的构造器初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。将forName和newInstance配合起来使用,可以根据存储在字符串中的类名创建一个对象。例如:
String className = "java.util.Date";
Object m = Class.forName(className).newInstance();
对于反射比较重要的部分是其分析类的能力,检查类的结构。在java.lang.reflect包中有三个类Filed、Method和Constructor,他们分别用于描述类的域、方法和构造器。这三个类都有一个叫做getName()的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。这三个类还有一个叫做getModifiers()的方法,它将返回一个整型数组。每个整型都是用不同的位开关(0或1)来描述public和static这样的修饰符的使用状况。例如用8位的整型来表示这些public和static修饰符的组合,则可能的结果是10010010,相应位为1表示有相应的修饰符,为0则表示不存在相应的修饰符。另外,我们还可以利用java.lang.reflect包中Modifier类的静态方法分析getModifiers()返回的整型数值,同时我们也可以利用Modifier.toString()方法将修饰符打印出来。
在Class类中的getFileds()、getMethods()和getConstructors()方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFileds()、getDeclareMethods()和getDeclareConstructors()方法则将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。下面我们通过一个实例来演示反射函数的使用:
package reflection;
import java.lang.reflect.*;//插入反射所需要的类
import java.util.*;
public class ReflectionTest {
public static void main(String[] args) {
String name = "java.lang.Double";//类的名称
//异常处理机制
try {
Class cl = Class.forName(name);//创建类的对象
Class supercl = cl.getSuperclass();//创建超类的对象
String modifiers = Modifier.toString(cl.getModifiers());//返回public、static等修饰符
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print("class " + name);
if (supercl != null && supercl != Object.class) {
System.out.print(" entends " + supercl.getName());
}
System.out.print("\n{\n");
printConstructors(cl);//打印类对象的构造函数
System.out.println();
printMethod(cl);//打印类对象的方法
System.out.println();
printFields(cl);//打印类对象的域
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();//打印出栈轨迹
}
System.exit(0);
}
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getConstructors();//返回类提供的构造器数组
for (Constructor constructor : constructors) {
String name = constructor.getName();//返回构造器的名字
System.out.print(" ");
String modifiers = Modifier.toString(cl.getModifiers());//返回修饰构造器的描述符组合
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(name + "(");
Class[] paramTypes = constructor.getParameterTypes();//返回构造函数的参数数组
for (int i = 0; i < paramTypes.length; i++) {
if(i > 0)
System.out.print(", ");
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
public static void printMethod(Class cl) {
Method[] methods = cl.getDeclaredMethods();//返回类提供的方法数组
for (Method method : methods) {
Class retType = method.getReturnType();//返回方法的返回值类对象
String name = method.getName();//获取对象的名字
System.out.print(" ");
String modifiers = Modifier.toString(method.getModifiers());//返回修饰方法的描述符组合
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(retType.getName() + " " + name + "(");
Class[] paramTypes = method.getParameterTypes();//返回方法的参数类型数组
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();//返回类提供的public域数组
for (Field field : fields) {
Class type = field.getType();//返回域的类型
String name = field.getName();//返回域的名字
System.out.print(" ");
String modifiers = Modifier.toString(field.getModifiers());//返回修饰域的描述符组合
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.println(type.getName() + " " + name + ";");
}
}
}
程序将输出:
{
public final java.lang.Double(java.lang.String);
public final java.lang.Double(double);
public boolean equals(java.lang.Object);
public java.lang.String toString();
public static java.lang.String toString(double);
public int hashCode();
public static native long doubleToRawLongBits(double);
public static long doubleToLongBits(double);
public static native double longBitsToDouble(long);
public int compareTo(java.lang.Double);
public volatile int compareTo(java.lang.Object);
public byte byteValue();
public short shortValue();
public int intValue();
public long longValue();
public float floatValue();
public double doubleValue();
public static java.lang.Double valueOf(double);
public static java.lang.Double valueOf(java.lang.String);
public static java.lang.String toHexString(double);
public static int compare(double, double);
public boolean isNaN();
public static boolean isNaN(double);
public boolean isInfinite();
public static boolean isInfinite(double);
public static double parseDouble(java.lang.String);
public static final double POSITIVE_INFINITY;
public static final double NEGATIVE_INFINITY;
public static final double NaN;
public static final double MAX_VALUE;
public static final double MIN_NORMAL;
public static final double MIN_VALUE;
public static final int MAX_EXPONENT;
public static final int MIN_EXPONENT;
public static final int SIZE;
public static final java.lang.Class TYPE;
private final double value;
private static final long serialVersionUID;
}
反射机制还有一个比较重要的功能是允许你调用任意方法。在C/C++中是通过函数指针执行任意函数。而在Java中没有提供方法指针,而是通过Method类实现的。在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:Object invoke(Object obj,Object...args)。其中第一个参数是隐式参数,其余的对象提供了显示参数。对于静态方法,第一个参数可以被忽略,即可以将它设置为null。如果返回类型是基本类型,invoke方法会返回其包装器类型。那么如何获得Method对象呢?有两种方式:一种是通过调用getDeclareMethods()方法,然后对返回的Method对象数组进行查找,直到发现想要的方法为止;另外一种是通过getMethod()方法返回指定名字的那个方法。getMethod的签名是:
Method getMethod(String name,Class...parameterType)
下面通过一个示例来演示如何通过反射机制调用方法:
<span style="font-family:微软雅黑;">package methods;
import java.lang.reflect.*;
public class MethodPointerTest {
public static void main(String[] args) throws Exception {
Method sqrt = Math.class.getMethod("sqrt", double.class);//返回Method对象
Method square = MethodPointerTest.class.getMethod("square", double.class);
printTable(1, 10, 10, square);
printTable(1, 10, 10, sqrt);
}
public static double square(double x) {
return x*x;
}
public static void printTable(double from,double to,int n,Method f) {
System.out.println(f);
double dx = (to-from)/(n-1);
for(double x = from; x <= to; x += dx){
try {
double y = (Double) f.invoke(null, x);//通过反射机制调用函数
System.out.printf("%10.4f | %10.4f\n",x,y);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}</span><span style="font-family:Courier New;">
</span>
程序返回结果为:
1.0000 | 1.0000
2.0000 | 4.0000
3.0000 | 9.0000
4.0000 | 16.0000
5.0000 | 25.0000
6.0000 | 36.0000
7.0000 | 49.0000
8.0000 | 64.0000
9.0000 | 81.0000
10.0000 | 100.0000
public static double java.lang.Math.sqrt(double)
1.0000 | 1.0000
2.0000 | 1.4142
3.0000 | 1.7321
4.0000 | 2.0000
5.0000 | 2.2361
6.0000 | 2.4495
7.0000 | 2.6458
8.0000 | 2.8284
9.0000 | 3.0000
10.0000 | 3.1623
虽然反射机制可以通过invoke方法触发方法调用,但是这种通过Method对象实现方法调用的出错可能性也比较大。如果在调用方法的时候提供了一个错误的参数,那么invoke方法将会抛出一个异常。另外,invoke的参数和返回值必须是Object类型的,这就意味着必须进行多次的类型转换,这样做将会使编译器错过检查代码的机会。因此,仅在必要的时候才调用Method对象,最好还是使用内部类和接口来实现相同的功能。