一、概述
1.反射机制的定义
Java反射是在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;并可以得到类的实例对象,对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的过程称之为Java的反射机制。
反射的核心就是在JVM虚拟机运行中动态加载类或调用方法以及访问属性,它不需要事先(写代码期间或者编译时)知道是运行对象是哪个。
2.反射提供了哪些功能?
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所有的成员变量和方法(通过反射甚至可以访问private私有方法)
- 在运行时调用任意一个对象的方法
3.反射的应用场景
- 逆向代码,反编译.class -->java
- 单纯的反射机制应用框架 ,例如EventBus
- 动态生成类框架,例如Gson
二、通过Java反射查看类信息
(一)获取Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class类。通过该Class对象就可以访问到JVM虚拟机中的这个类。
有三种方式获取Class对象
1. 使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的完整包名。
package reflection; public class Reflection { }
try { Class name = Class.forName("reflection.Reflection"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
在JVM运行加载中,如果找不到该类,会抛出ClassNotFoundException异常,告诉开发者。
2. 调用某个类的class属性来获取该类对应的Class对象
Class clazz=Reflection.class;
3. 调用某个对象的getClass()方法。该方法是Object类的一个方法,是Java所有的对象都可以调用。
Reflection reflection = new Reflection(); Class<? extends Reflection> clazz = reflection.getClass();
总结:第一种和第二种相比,第二种代码相对更安全,程序在编译阶段就可以检测Class对象是否存在,性能上也更好,不用调用方法。
(二)从Class对象中获取信息
1. 获取Class对象对应类的属性、方法、构造函数等
ield[] allFields = clazz.getDeclaredFields();//获取class对象的所有属性 Field[] publicFields = clazz.getFields();//获取class对象的public属性 try { Field ageField = clazz.getDeclaredField("age");//获取class指定属性 Field desField = clazz.getField("des");//获取class指定的public属性 } catch (NoSuchFieldException e) { e.printStackTrace(); } Method[] methods = clazz.getDeclaredMethods();//获取class对象的所有声明方法 Method[] allMethods = clazz.getMethods();//获取class对象的所有方法 包括父类的方法 Class parentClass = clazz.getSuperclass();//获取class对象的父类 Class<?>[] interfaceClasses = clazz.getInterfaces();//获取class对象的所有接口 Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();//获取class对象的所有声明构造函数 Constructor<?>[] publicConstructors = clazz.getConstructors();//获取class对象public构造函数 try { Constructor<?> constructor = clazz.getDeclaredConstructor(new Class[]{String.class});//获取指定声明构造函数 Constructor publicConstructor = clazz.getConstructor(new Class[]{});//获取指定声明的public构造函数 } catch (NoSuchMethodException e) { e.printStackTrace(); } Annotation[] annotations = clazz.getAnnotations();//获取class对象的所有注解 Annotation annotation = clazz.getAnnotation(Deprecated.class);//获取class对象指定注解 Type genericSuperclass = clazz.getGenericSuperclass();//获取class对象的直接超类的 Type Type[] interfaceTypes = clazz.getGenericInterfaces();//获取class对象的所有接口的type集合
2. 获取注解信息
try { //首先需要获得与该方法对应的Method对象 Method method = clazz.getDeclaredMethod("jumpToGoodsDetail", new Class[]{String.class, String.class}); Annotation[] annotations1 = method.getAnnotations();//获取所有的方法注解信息 Annotation annotation1 = method.getAnnotation(RouterUri.class);//获取指定的注解信息 TypeVariable[] typeVariables1 = method.getTypeParameters(); Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();//拿到所有参数注解信息 Class<?>[] parameterTypes = method.getParameterTypes();//获取所有参数class类型 Type[] genericParameterTypes = method.getGenericParameterTypes();//获取所有参数的type类型 Class<?> returnType = method.getReturnType();//获取方法的返回类型 int modifiers = method.getModifiers();//获取方法的访问权限 } catch (NoSuchMethodException e) { e.printStackTrace(); }
3. 获取一些判断信息:是否为接口、枚举、注解类型等等
boolean isPrimitive = clazz.isPrimitive();//判断是否是基础类型 boolean isArray = clazz.isArray();//判断是否是集合类 boolean isAnnotation = clazz.isAnnotation();//判断是否是注解类 boolean isInterface = clazz.isInterface();//判断是否是接口类 boolean isEnum = clazz.isEnum();//判断是否是枚举类 boolean isAnonymousClass = clazz.isAnonymousClass();//判断是否是匿名内部类 boolean isAnnotationPresent = clazz.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰 String className = clazz.getName();//获取class名字 包含包名路径 Package aPackage = clazz.getPackage();//获取class的包信息 String simpleName = clazz.getSimpleName();//获取class类名 int modifiers = clazz.getModifiers();//获取class访问权限 Class<?>[] declaredClasses = clazz.getDeclaredClasses();//内部类 Class<?> declaringClass = clazz.getDeclaringClass();//外部类
三、反射的基本运用(使用反射生成并操作对象)
通过Class对象可以获取对应类的方法(Method)、构造器(Constructor)、成员变量(Field)
1. 创建实例对象
通过反射生成对象主要有两种方式
实体类:
package reflection; public class Reflection { private String x; private int y; public Reflection() { } public Reflection(String x) { this.x = x; } public Reflection(int y) { super(); this.y = y; } public Reflection(String x, int y) { super(); this.x = x; this.y = y; } public String getX() { return x; } public void setX(String x) { this.x = x; } @Override public String toString() { return "Reflection [x=" + x + ", y=" + y + "]"; } }
(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。这种方式要求改Class对象的对应类有默认构造器。
//获取Class对象的实例 try { Class<?> clazz=Reflection.class; Object object = clazz.newInstance(); System.out.println(object.toString()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
输出结果:
Reflection [x=null, y=0]
(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance方法来创建实例。这种方法可以用指定的构造器构造类的实例。
// 1 -- 获取Reflection对应的Class对象 Class<?> clazz = Reflection.class; // 2 -- 通过Class对象调用getConstructor(xxx.Class)获取Constructor对象 // 注意xxx.Class是Reflection类的构造函数中形参的Class对象 // getConstructor(……)也可不传入形参,不过此时newInstance(……)也不能传入形参,否则会抛出wrong number of arguments Constructor constructor = clazz.getConstructor(String.class); // 3 -- 最后通过constructor.newInstance(……)指定形参的构造器获取Reflection对象 Object object = constructor.newInstance("测试"); System.out.println(object.toString());
输出结果:
Reflection [x=测试, y=0]
从源码可以看出getConstructor(……)和newInstance(……)可以传入多个形参Class对象,但必须一一对应,也就是说传入的形参必须是实体类的构造方法中所需要的形参Class对象
public Reflection(String x, int y) { super(); this.x = x; this.y = y; } Constructor constructor = clazz.getConstructor(String.class,int.class); Object object = constructor.newInstance("测试",10); System.out.println(object.toString()); //输出结果: Reflection [x=测试, y=10]
2. 获取方法
当获取某个类的Class对象后,就可以通过Class对象得到的全部方法或者指定方法
获取Class对象的方法主要有以下两种方式:
(1)getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
(2)getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象.
private static void testMethod() throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException{ Class<?> clazz = Reflection.class; //方法一:返回所有的public公共方法,包括继承类的方法 System.out.println("------------getMethods()-------------"); Method[] methods = clazz.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; //输出所有的方法名称 System.out.println(method.getName()); } System.out.println(); //方法二 System.out.println("------------getMethod(xx,yy.class)-------------"); Method method = clazz.getMethod("setX", String.class); System.out.println(method.getName()); }
输出结果:
------------getMethods()------------- toString setX getX wait wait wait equals hashCode getClass notify notifyAll ------------getMethod()------------- setX
上面知道了方法的获取,接下来就是如何调用方法赋值或者获取值
每个Method对象对应一个方法,获取Method对象后,可通过该Method来调用它对应的方法。在Method里包含一个invoke()方法,可以通过这个方法来操作Class对象对应类的方法。
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {……}
invoke(obj,args)方法中obj表示调用方法的对象(就是哪个对象调用),args表示方法中所需传入的参数。
//获取Reflection的Class对象 Class<?> clazz = Reflection.class; //创建Reflection实例 Reflection object = (Reflection)clazz.newInstance(); //通过Class对象调用指定的方法 Method method = clazz.getMethod("setX", String.class); //给setX方法赋值 method.invoke(object, "测试方法"); //得到赋值后的值 System.out.println(object.getX());
输出结果:
测试方法
3. 获取类的成员变量信息
通过反射访问类的成员变量主要有以下几种
(1)getFiled: 访问公有public的成员变量。
(2)getDeclaredField:所有已声明的成员变量,但不能得到其父类的成员变量。
(3)getFileds和getDeclaredFields用法同上,返回的是个成员变量的数组(可参照Method)
private static void testFiled() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Class<?> clazz = Reflection.class; Reflection reflection = new Reflection(); //也可以通过newInstance创建实例 // Reflection reflection=(Reflection) clazz.newInstance(); System.out.println("------------getDeclaredField(……)----------------"); Field declaredField = clazz.getDeclaredField("x"); //setAccessible的作用是允许通过反射来调用private方法、构造器、成员变量,是AccessibleObject类下的方法。 declaredField.setAccessible(true); //给成员变量赋值,第一参数obj表示类的对象,第二个参数value表示给指定变量赋值 declaredField.set(reflection, "测试获取成员变量1"); System.out.println(reflection); System.out.println(); System.out.println("------------getField(……)----------------"); Field field = clazz.getField("z"); field.set(reflection,"测试获取成员变量2" ); System.out.println(reflection); }
public class Reflection { private String x; public String z; @Override public String toString() { return "Reflection [ y=" + y + ", z=" + z + "]"; } }
输出结果:
------------getDeclaredField(……)---------------- Reflection [x=测试获取成员变量1, y=0, z=null] ------------getField(……)---------------- Reflection [x=测试获取成员变量1, y=0, z=测试获取成员变量2]以上需要注意的是,通过 getDeclaredField方法私有private变量时,必须设置把setAccessible(true),否则抛出如下异常,提示不能访问私有变量
Class reflection.ReflectionTest can not access a member of class reflection.Reflection with modifiers "private"其实 setAccessible不是单单Field所拥有的,它是AccessibleObject类下的方法,同时AccessibleObject类 是Field,Method和Constructor对象的基类;所以Field、Method、Constructor都可以访问该方法,用来允许通过反射调用private成员变量、方法、构造函数。
* @see Field * @see Method * @see Constructor * @see ReflectPermission * * @since 1.2 */ public class AccessibleObject implements AnnotatedElement {……}
4. 通过反射获取数组
Array类为java.lang.reflect.Array类。我们通过Array.newInstance()动态创建数组对象,源码:
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException { return newArray(componentType, length); }
第一个参数componentType是指定数组元素类型,第二个参数length是数组长度
最后通过set(Object array, int index, Object value)和get(Object array, int index)方法进行赋值和获取值的操作:
private static void testArrays() { Object arr = Array.newInstance(String.class, 2); Array.set(arr, 0, "数组1"); Array.set(arr, 1, "数组2"); Object str1 = Array.get(arr, 0); Object str2 = Array.get(arr, 1); System.out.println(str1+"--"+str2); }
输出结果:
数组1--数组2
四、总结
反射机制在平时业务开发中很少被用上,但是在一些基础框架会经常被用到,所以了解学习反射机制是很有必要的。
优点:
由于运行时类型,可以在编译时未知类情况下,类动态加载并访问。
缺点:
反射需要在运行时额外调度JVM虚拟机做某些操作,性能会比java编译型代码慢很多。