在Java中反射是一种十分强大的机制,可以传递class,可以取得一个类的所有信息,动态的生成一个类。甚至可以取得一个类的父类。同样反射这一机制也是Java高级开发所必须掌握的。
能够分析类能力的程序被称为反射。本文将谈到java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等等。
Class类
凡是接触过Java的,甚至是知道Java的程序员都知道Java是一门纯面向对象的语言。在Java中可以说万事万物皆对象。甚至是程序员自己编写的任意一个类。这些类是java.lang.Class的实例对象。我们可以通过Class类来访问一个特定类的信息。
Date date1 = new Date();
Class c1 = date1.getClass();//getClass()返回date1的class type
如果输出c1,我们会在控制台看到:
class java.util.Date
我们可以清晰的看出,date1是一个class type 为class的一个java.util.Date对象。
如果类名是保存在一个字符串中,并且在运行的时候可能发生改变,那么我们可以调用静态方法forName来获取对应的Class对象。这个方法只有在所提供的字符串是完整的类名或者接口名的时候才能使用,否则将会抛出checkedException。
String className="java.util.Date";
try {
Class c2=Class.forName(className);//forName返回给定字符串的类或者接口的名称以及class type
System.out.println(c2);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
获取Class对象的第三种方法十分的简单。如果T是任意的Java类型。那么T.class将代表匹配的类对象。
Class c4=Date.class;
Class c5= int[].class;
还有一个很有用的方法newInstance可以用来快速的创建一个类的实例。所以我们可以用forName和newInstance相结合根据一个储存在字符串中的类名来创建一个对象。
String className="java.util.Date";
Object o;
try {
o = Class.forName(className).newInstance();
//forName和newInstance都是返回Class的。注意强制类型转换
Date date= (Date) Class.forName(className).newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
利用反射分析类
在java.lang.reflection包中有三个类Field,Method,Constructor分别描述类的域,方法和构造器。它们都有一个叫做getName的方法用来返回项目的名称(包括包名)。通过调用这三个类中的一些方法,程序员很容易就能知道一个类的结构。
下面的代码就显示了如何打印一个类的全部信息的方法。
package org.joea.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
/**
* 利用反射来显示一个类的详细信息
*
* @author Joea
*
*/
public class ReflectionTest {
public static void main(String[] args) {
String name;
if (args.length > 0)
name = args[0];
else {
Scanner in = new Scanner(System.in);
System.out.println("please enter class name(eg:java.util.Date):");
name = in.next();
}
try {
Class c = Class.forName(name);
Class superc = c.getSuperclass();
String modifier = Modifier.toString(c.getModifiers());// 获取修饰符
if (modifier.length() > 0)
System.out.print(modifier + " ");
System.out.print("class " + c.getName());
if (superc != null && superc != Object.class)
System.out.print(" extends " + superc.getName());
System.out.println("{");
printFiles(c);
printConstructors(c);
printMethods(c);
System.out.println("}");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 获取类的所有的构造方法
*
* @param c
*/
public static void printConstructors(Class c) {
Constructor[] constructor = c.getDeclaredConstructors();// 返回包含该类声明的所有构造方法的数组,不包括从父类继承而来的方法
for (Constructor constructor2 : constructor) {
String name = constructor2.getName();
System.out.print(" ");
String modifiers = Modifier.toString(constructor2.getModifiers());// 获取方法修饰符
if (modifiers.length() > 0)
System.out.print(modifiers + " ");
System.out.print(name + "(");
Class prameterTypes[] = constructor2.getParameterTypes();// 返回该方法所有参数的数组
for (Class p : prameterTypes) {
System.out.print(p.getName() + ",");
}
System.out.println(")");
}
}
/**
* 打印类中所有的方法,不包括从父类继承的方法
*
* @param c
*/
public static void printMethods(Class c) {
Method methods[] = c.getDeclaredMethods();// 返回包含该类声明的所有方法的数组,不包括从父类继承而来的方法
for (Method method : methods) {
System.out.print(" ");
String modifier = Modifier.toString(method.getModifiers());
if (modifier.length() > 0)
System.out.print(modifier + " ");
String methodName = method.getName();
System.out.print(methodName + "(");
Class prameterType[] = method.getParameterTypes();
for (Class prameter : prameterType) {
System.out.print(prameter.getName() + ",");
}
System.out.println(")");
}
}
/**
* 打印类中的所有成员变量,不包括从父类继承而来的
*
* @param c
*/
public static void printFiles(Class c) {
Field[] fields = c.getDeclaredFields();// 返回包含该类声明的所有成员变量的数组,不包括从父类继承而来的方法
for (Field field : fields) {
Class type = field.getType();
String name = field.getName();
System.out.print(" ");
String modifier = Modifier.toString(field.getModifiers());
if (modifier.length() > 0)
System.out.print(modifier + " ");
System.out.println(type.getName() + " " + name + ";");
}
}
}
利用反射了解泛型的本质
通过上面的介绍,读者们肯定都已经知道了如何查看任意对象的域名和class type。在介绍泛型本质的之前。我们来看一下以下代码:
package org.joea.test;
import java.util.ArrayList;
public class SimpleTest {
public static void main(String[] args) {
ArrayList list1=new ArrayList();
ArrayList<String> list2=new ArrayList();
Class c1=list1.getClass();
Class c2=list2.getClass();
System.out.println(c1==c2);
}
}
运行上面的代码,会发现返回的是true。其实笔者刚开始遇到这个问题的时候十分困惑。如果你向list2中传递一个String的话,编译会正常通过。如果传递一个非String的值,那么编译就会报错。所以就理所当然的人文list2的class type是String。当时转念想一想,如果list2的class type是String,那么list1的是什么。经过验证发现list1和list2的class type都是class java.util.ArrayList。
抱着这个疑问查阅了一些质料。得到结果是.:Java范型时编译时技术,在运行时不包含范型信息,仅仅Class的实例中包含了类型参数的定义信息。泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源
码到源码的转换,它把泛型版本转换成非泛型版本。
基本上,擦除去掉了所有的泛型类型信息。所有在尖括号之间的类型信息都被扔掉了,因此,比如说一个
List类型被转换为List。所有对类型变量的引用被替换成类型变量的上限(通常是Object)。而且,
无论何时结果代码类型不正确,会插入一个到合适类型的转换。
T badCast(T t, Object o) {
return (T) o; // unchecked warning
}
类型参数在运行时并不存在。这意味着它们不会添加任何的时间或者空间上的负担,这很好。不幸的是,这也意味着你不能依靠他们进行类型转换。这样也就解释了为什么上面那段代码的结果是true了。
利用反射调用任意方法
学过C/C++的读者应该知道,由于函数指针的存在,使得在C/C++中可以从函数指针执行任意函数。Java中没有提供指针,但是反射机制允许程序员调用任意方法。
在Method类中存在一个invoke方法,该方法签名如下:
Object incoke(Object obj,Object...args)
第一个参数是隐式参数。对于静态方法第一个参数可一忽略或者设置为null。
如果返回值是基本的数据类型,invoke方法将返回其包装器类型。否则,则需要进行相应的类型转换。
那么,如何得到Method对象呢?前面介绍过通过调用getDeclareMethods方法,然后通过对返回的Method数组进行查找。这种方法繁琐并且效率低。程序员也可以调用Class类中的getMethod方法得到想要的方法。该方法签名如下:
Method getMethod(String name,Object...ParameterTypes)
这样我们就可以通过给getMethod传递方法名,方法的参数类型来找到我们需要的唯一方法。
package org.joea.methodreflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodReflectionTest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Method sqrt=Math.class.getMethod("sqrt", double.class); //获取对应的方法
Method square=MethodReflectionTest.class.getMethod("square", double.class);
Print(0, 10, 10, sqrt);
Print(0,10,10,square);
}
public static double square(double x){
return x*x;
}
public static void Print(double from,double to,double row,Method method){
System.out.println(method.getName());
for(double x=from;x<=to;x++){
double y;
try {
y = (double) method.invoke(null, x);//method为static方法,不属于任何一个对像。所以第一个参数为null
System.out.printf("%10.4f | %10.4f %n",x,y);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
上面的程序清楚的显示了,在Java中可以通过Method对象实现C/C++中的函数指针的所有操作。但是invoke方法的返回值必须是Object,这就意味着必须进行多次类型转换。这样可能会产生意想不到的错误。所以在开发中尽量不要使用Method方法的回调。利用接口进行回调是一个良好的编码风格。