Java学习之反射机制

一、概述

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编译型代码慢很多。





猜你喜欢

转载自blog.csdn.net/hzw2017/article/details/80445696
今日推荐