基本理解java Reflection(反射)
前言
一句话:反射是java中相对高级的功能,它给予了JVM在运行时去检查或者修改应用程序的能力
应用场景
1.通过全限定名去实例化外部对象或者自定义对象,比如Spring的IOC,通过XML或者注解生成用户自定义对象
2.类浏览器,可视化的开发环境。比如IDE工具等等
3.调试器和测试工具
反射的缺点
1.一定的性能开销,因为反射涉及到动态类型的解析,某些JVM优化在反射上是没用的,所以反射操作的性能比非反射的慢。
2.安全限制,反射在security manager下运行可能不起作用,这对于必须在受限制的安全上下文中运行的代码是重要的考虑
3.内部暴露,反射可以访问class的private类型的方法跟字段,可能与原来代码的本意背道而驰,产生bug
反射API的使用
类的反射基本使用
类成员(字段,方法,构造器)
数组和枚举(PS:这2种是特殊类型的类)
反射在类中的应用
Class对象是使用反射API的唯一入口,所以我们先要生成Class对象:
//全限定名去获取相应的class对象 Class<?> clazz1 = Class.forName("java.lang.String"); //对象的实例如果存在,也可以使用getClass方法 Class<?> clazz2 = "321".getClass(); //获得基本类型的Class对象 Class<?> intClazz = int.class; //数组类型获取class对象 Class<?> cDoubleArray = Class.forName("[D");//与double[].class一致 Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");//与String[][].class一致
有了Class对象后,我们基本上就可以干任何与反射事情了,我们先检索下这个类对象的类基本信息:
try {
Class<?> c = Class.forName("mycom.activiti.Man"); //获得此类的修饰符类型 System.out.println("Class:\n" +c.getCanonicalName()); System.out.println("修饰符:\n"+ Modifier.toString(c.getModifiers())); //获得类的泛型 System.out.println("类泛型:"); TypeVariable<?>[] tv = c.getTypeParameters(); for (TypeVariable<?> typeVariable : tv) { System.out.println(typeVariable.getName()); } //获得类实现的接口 System.out.println("实现的接口:"); Class<?>[] classes = c.getInterfaces(); for (Class<?> it : classes) { System.out.println(it); } //继承的父类 System.out.println("继承的父类:"); Class<?> c1 = c.getSuperclass(); String fatherClasses = ""; while(c1!=null){ fatherClasses += c1.getName()+" "; c1 = c1.getSuperclass(); } System.out.println(fatherClasses); //类上的注解 System.out.println("类上的注解:");
Annotation[] annotations = c.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a.annotationType());
}
// 获得此类中的所有(public、private、protected、包权限)类,接口,枚举 classes = c.getDeclaredClasses(); System.out.println("类中的类、接口、枚举:"); for (Class<?> cz : classes) { System.out.println(cz); } // 获得此类以及父类中所有的public类(内部类),接口,枚举 System.out.println("类以及父类中的公共类、公共接口、公共枚举:"); classes = c.getClasses(); for (Class<?> cz : classes) { System.out.println(cz); } } catch (Exception e) { e.printStackTrace(); }
上面的代码中是检索类的信息,要说明的是在检索注解的时候,只能检索运行时的注解,而一般的@override,@Deprecated非运行时注解是无法检索的。
接下来我们检索一下类中的成员信息:
检索类中的成员信息主要有2种类别的方法
Class API | List of members? | Inherited members? | Private members? |
---|---|---|---|
getDeclaredField() |
no | no | yes |
getField() |
no | yes | no |
getDeclaredFields() |
yes | no | yes |
getFields() |
yes | yes | no |
Class API | List of members? | Inherited members? | Private members? |
---|---|---|---|
getDeclaredMethod() |
no | no | yes |
getMethod() |
no | yes | no |
getDeclaredMethods() |
yes | no | yes |
getMethods() |
yes | yes | no |
Class API | List of members? | Inherited members? | Private members? |
---|---|---|---|
getDeclaredConstructor() |
no | N/A1 | yes |
getConstructor() |
no | N/A1 | no |
getDeclaredConstructors() |
yes | N/A1 | yes |
getConstructors() |
yes | N/A1 | no |
检索字段跟方法我就不说了,一看就懂,获得构造器中父类的构造器是获得不到的,因为构造器不能继承。
我们写个demo来检索下成员信息:
// 获得类的包 Package p = c.getPackage(); System.out.println("包:"); System.out.println(p.getName()); System.out.println("类的构造器:"); Constructor<?>[] cs = c.getDeclaredConstructors(); for (Constructor<?> constructor : cs) { System.out.println(constructor.toGenericString()); } System.out.println("类的所有字段:"); Field[] fields = c.getDeclaredFields(); for (Field field : fields) { System.out.println(field.toGenericString()); } System.out.println("类及父类中的非private字段:"); fields = c.getDeclaredFields(); for (Field field : fields) { System.out.println(field.toGenericString()); } System.out.println("类的所有方法:"); Method[] methods = c.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.toGenericString()); } System.out.println("类及父类中的非private方法:"); methods = c.getMethods(); for (Method method : methods) { System.out.println(method.toGenericString()); }
检索就说到这里,只要稍微熟悉了Class的几个方法,举一反三都很简单。
接下来我们进入重点,反射生成实例,字段注入,以及方法调用。
首先有一个简单的man类,有一个私有构造器,和一个public与private字段,还有一个printMsg方法如下:
package mycom.activiti;
public class Man { public String name = "321"; private int age; private Man() {} public void printMsg(String str) { System.out.println(name + ":" + age + ":" + str); } }
操作如下:
public static void main(String... args) throws Exception { Class<?> c = Class.forName("mycom.activiti.Man");// 替换成自己的类 Constructor<?>[] constructors = c.getDeclaredConstructors(); Constructor<?> con = null; for (Constructor<?> constructor : constructors) { if (constructor.getParameterTypes().length == 0) {// 轮询匹配无参构造器 con = constructor; } } con.setAccessible(true);// 以防是私有构造器,通过此方法获取访问权限 Man man = (Man) con.newInstance(); Field field = c.getDeclaredField("name");// 字段注入 field.set(man, "张三丰"); field = c.getDeclaredField("age"); field.setAccessible(true);// 私有字段获得访问权限 field.setInt(man, 28); Method m = c.getMethod("printMsg", String.class); m.invoke(man, new Object[] { "你好" });//调用方法 }
结果:
张三丰:28:你好
说几点需要注意的地方,我这里是偷懒所以直接抛Exception,再者说下class直接new Instance() 和 构造器newInstance()的区别,
首先这两者都是可以实例化对象,但是通过构造器的newInstance()会比class的好得多,
Class.newInstance() 不能实例化private构造器的对象,而Constructor.newInstance()可以通过设置构造器访问权限生成实例对象
还有一点,Constructor.newInstance()会包装在生成对象时抛出的异常信息,便于异常的后续处理与定位错误
我们把实体的构造器改为抛出异常:
public Man() { throw new RuntimeException("321"); }
当用Class.newInstance()的时候,会直接抛出运行时异常,
而Constructor.newInstance()会包装成InvocationTargetException异常,这样可以通过try Catch 捕获做后续处理!
还需要说明一点的事,8大基本类型与数组和void都有对应的class,方便在反射时判断或检索字段或者修饰符信息
int - int.class,
int[] - int[].class //一维数组
void - void.class //空方法修饰符
......
建议大家多看官方文档上面说的非常详细!
更多的信息请移步: http://docs.oracle.com/javase/tutorial/reflect/index.html