1.2 反射
本节首先介绍反射的基本概念,理解什么是反射,以及Class类和反射常用API,通过实例操作来学习反射的使用。
1.2.1 反射机制
在上面自定义注解时我们也有提到反射,要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象。那么什么是反射呢?在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。它有点类似照妖镜的作用,不管是什么妖魔鬼怪(类或对象)都能看到它的真面目(获取类的属性方法、调用对象的属性方法)。
1.2.2 理解Class类
反射机制可以动态获取类信息以及调用对象方法,那它是通过什么实现的呢?这就要介绍一下Class类了。首先明确Class也是一个类,只是它是一个描述类的类,也可以生成对象。对于每个类而言,在JRE中有且仅有一个不变的Class类型的对象,而这个Class类型的对象只能由系统建立,封装了当前对象所对应的类的信息,有哪些属性、方法、构造器以及实现了哪些接口等。每个类的实例都会记得自己是由哪个Class实例所生成的。
要获取类信息或调用对象方法,肯定首先要获取到该类或对象对应的Class类的实例。一般获取Class对象有3种方式。
● 通过类名获取,类名.class。
● 通过对象获取,对象.getClass()。
● 通过全类名获取,Class.forName(全类名)。
这里我们可以使用字符串来做验证。
输出结果:
class java.lang.String class java.lang.String class java.lang.String
通过3种方式获取到Class实例后,再了解一下Class类常用的方法(见表1-1)。
表1-1 Class类常用的方法
1.2.3 反射的使用
这里要着重介绍一下上面API的使用,因为在后面要学习的Spring中IOC的原理就是反射加工厂模式。学好反射API有助于理解Spring框架内部实现。为了演示Class方法的使用,在注解demo的基础上对Person、Student类进行了修改。
Person类:
Student类:
1.描述方法Method
描述方法主要是4个获取方法(getMethods、getMethod、getDeclaredMethods、getDeclaredMethod)和1个调用方法(invoke)。
● getMethods:获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法,包括私有父类的私有方法。
● getMethod:获取clazz对应类中指定方法名和参数类型的方法,不能获取private方法,且获取从父类继承来的所有方法,包括私有父类的私有方法。因为存在同方法名不同参数这种情况,所以只有同时指定方法名和参数类型才能唯一确定一个方法。
● getDeclaredMethods:获取所有方法,包括私有方法,所有声明的方法,都可以获取到,且只获取当前类的方法。
● getDeclaredMethod:获取clazz对应类中指定方法名和参数类型的方法,包括私有方法,所有声明的方法,都可以获取到,且只获取当前类的方法。
● invoke:执行方法,第一个参数表示执行哪个对象的方法,剩下的参数是执行方法时需要传入的参数,私有方法的执行必须在调用invoke之前加上一句“method.setAccessible(true);”。
输出结果:
上面我们基本可以实现通过类名创建对象、通过方法名执行方法。类名和方法名都是字符串,我们可以把它们放到一个配置文件中,根据配置文件来执行方法,这样就有点类似基于XML的Spring了。
2.描述字段Field
描述字段Field方法的使用和描述方法Method中方法的使用有点类似,也是4个获取字段的方法(getFields、getField、getDeclaredFields、getDeclaredField)。
● getFields:获得某个类的所有公共(public)字段,包括父类中的字段。
● getField:获取某个类public成员变量中指定变量名的字段,包括基类。
● getDeclaredFields:获得某个类所有声明的字段,包括public、private和protected,但是不包括父类的声明字段。
● getDeclaredField:获取某个类的所有成员变量指定变量名的字段,不包括基类。
输出结果:
---------getDeclaredFields--------- StudentId ---------getFields--------- StudentId ---------getDeclaredField--------- StudentId ---------getField-------- StudentId
上面通过反射获取字段,得到字段之后就是获取或设置字段的值了。如果字段是私有的,那么不管是读值还是写值,都必须先调用setAccessible(true)方法,比如在Person类中,字段name字段是私有的。
Class clazz = Class.forName("Reflection.Person"); Person person = new Person("CYW"); //获取私有字段的值 Field field = clazz.getDeclaredField("Name"); //由于是私有字段,因此需要使用setAccessible(true) field.setAccessible(true); Object val = field.get(person); System.out.println(val); //改变私有字段的值 field.set(person, "ivan"); System.out.println(person.getName());
输出结果:
CYW ivan
3.描述构造器Constructor
先介绍一下描述构造函数Constructor用到的方法,主要还是4个:getConstructors、getDeclaredConstructors、getConstructor、getDeclaredConstructor。和前面Method、Field用的方法进行比较,举一反三,我们也能大概了解这几个方法的使用。其实,在编程中有好多体现哲学思想的地方,有正有反,有阴有阳,学会思考,这样可以以点带面、触类旁通。
● getConstructors:获取对应类中public类型的构造函数,且只获取当前类的构造函数。
● getConstructor:获取对应类中public指定参数类型的构造函数,且只获取当前类的构造函数。
● getDeclaredConstructors:获取对应类中所有构造函数,包括私有构造函数,且只获取当前类的构造函数。
● getDeclaredConstructor:获取对应类中指定参数类型的方法,包括私有构造函数,且只获取当前类的方法。
输出结果:
4.描述注解Annotation
描述注解主要用到getAnnotation(Class<A> annotationClass)方法,返回该元素指定类型的注解,否则返回null。
输出结果:
description:学生 description:人