反射
一.概述
Java反射机制是在运行状态中,对于任意一个类(class文件),都能够查询并操作这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
总而言之,反射就是把Java类中的各种成分映射成相应的Java类。比如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分可分为:成员变量,方法,构造方法,包等。这些成分都可以用一个个的Java类来表示。表示java类的Class类显然要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息用相应的类的实例对象来表示,它们分别是Field,Method,Contructor,Package等等。
二.Class类字节码文件
用于描述字节码的类就是Class类。只要创建Class类对象,就可以提取出字节码文件中的各种内容包括:字段、构造函数、一般函数。反射就是依靠Class类来完成的。如果要想对一个类文件中的内容进行操作,就要先获取到该类的字节码文件对象。
获取Class对象的三种方式:(代码演示)
① 第一种方式:
<strong><span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;"><strong><strong><span style="font-size:14px;">/*
* 1.使用object类的getClass()方法;
* (此种方式必须明确具体的类,并创建对象)
*/
private static void getClassObject_1() {
Person p1 = new Person() ;
Class clazz = p1.getClass() ;
System.out.println(clazz);
}</span></strong></strong></span></span></strong></span></strong>
② 第二种方式:
<strong><span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;"><strong> /*
* 2.通过利用任何数据所具备的一个静态属性.class来获取其对应的class对象
* (此种方式不够扩展,还是需要明确用到类中的静态成员)
*/
private static void getClassObject_2() {
Class clazz = Person.class ;
System.out.println(clazz);
}
</strong></span></span></strong></span></strong>
③ 第三种方式:
<strong><span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;"><strong> /*
* 3.使用Class类中的forname(String str)方法,通过传入类的名字来获取其对应的class对象
* (传入类名时,需将类的整个包名传入。该方法更为扩展)
*
*/
private static void getClassObject_3() throws ClassNotFoundException {
String className = "cn.itzixue.bean.Person" ;
Class clazz = Class.forName(className) ; // 需要完整的类名
System.out.println(clazz);
}</strong></span></span></strong></span></strong>
相比三种获取方式,第一种方式最不灵活,因为这种方式每次都需要具体的类和该类的对象,以及调用getClass方法;第二种方式虽然比第一种较为简单,不用创建对象,也不用调用getClass方法,但是还是要使用具体的类,和该类中的一个静态属性class完成。而第三种方式最简单,只要知道类的名称即可。这种方式不需要使用该类,也不需要去调用具体的属性和行为就可以获取到Class对象。因为第三种方式仅知道类名就可以获取到该类字节码对象的方式,所以更有利于扩展,开发中最常使用。
编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。其中,使用new创建对象是静态加载类,在编译时可就需要加载所有的可能使用到的类。而动态加载类是在运行时刻进行加载,反射利用最多的就是动态加载类。
三.获取Class中的构造函数
获取构造函数一般使用的是Class类对象的getConstructor()方法来获取(该方法返回一个Constructor类对象)。如果构造方法是需要传入参数的,则在创建对象时要使用Constructor类对象的newInstance()方法;若是空参构造函数,则可以使用Class类中的newInstance()方法。
代码演示:
① 调用类的空参构造方法创建一个新的对象(传统方法):(注意:注释中包含重要知识点和注意点)
<strong><span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;"><strong> /*
通过new来实例化对象,在new的时候,先根据被new的类的名称找寻该类的字节码文件,
并加载进内存,接着创建该字节码文件对象,并接着创建该字节码文件对应的Person对象。
*/
public static void creatNewObject_1() {
Person p1 = new Person() ;
}</strong></span></span></strong></span></strong>
② 调用类的空参构造方法创建一个新的对象(利用反射机制): (注意:注释中包含重要知识点和注意点)
<strong><span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;"><strong> /*
先利用Class类的fotName(String str)方法找寻所传入名称的类文件吗,并加载进内存,接着产生Class对象。
通过利用Class对象的newInstance()方法来创建所传入类的对象。(该方法扩展性强)
*/
public static void creatNewObject_2() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
String className = "cn.itzixue.bean.Person" ;
Class clazz = Class.forName(className) ;
Object obj = clazz.newInstance() ;
}</strong></span></span></strong></span></strong>
③调用类的带参构造方法创建一个新的对象(利用反射机制): (注意:注释中包含重要知识点和注意点)
<strong><span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;"><strong> /*
先通过Class类对象的getConstructor(Class<?>... parameterType)方法
来获取制定大的公共构造方法。(该方法返回一个Constructor对象,若要获取包括私有在内的指定构造方法,
使用getDeclaredConstructor(Class<?>... parameterType)方法)。
再通过调用所获得的对象的newInstance()方法进行对象初始化。
*/
public static void creatNewObject_3() throws Exception {
String className = "cn.itzixue.bean.Person" ;
Class clazz = Class.forName(className) ;
//获取指定的构造函数对象
Constructor constructor = clazz.getConstructor(String.class,int.class) ;
//通过该构造器对象初始化对象(结果返回的是Object对象)
Object obj = constructor.newInstance("权权",21) ;
}</strong></span></span></strong></span></strong>
通过Class类对象的getConstructors()方法可以获取Class字节码文件中的所有公有的(包括父类)构造函数:getDeclaredConstructors()方法可以获取Class字节码文件中的所有(仅本类但包括私有的)构造函数。
四.获取Class中的字段
获取class字节码文件中的字段一般使用的是Class类对象的getField()方法来获取(该方法返回一个Field类对象)。
Class类对象中操作字段的相关方法代码演示:
① (返回值类型:Field) getField(String str) ; // 此方法只能获取本类或者父类中的公有的字段
② (返回值类型:Field) getDeclaredField(String str) ; // 此方法只能获取该类中的字段,但是包括私有
Field类中
① (返回值类型:void) setAccessible(true) ; // 通过此方法,可以让该私有字段进行取消权限检查的能力,以便于更轻松的访问(也称之为暴力访问)
② (返回值类型:void) set(Object obj, Object value) ; // 将指定对象变量上此Field对象所表示的字段设置为指定的新值
③ (返回值类型:void) get(Object obj) ; // 返回指定对象上Field表示的字段的值
代码演示:(注意:注重中包含重要知识点和注意点)
<strong><span style="font-family:KaiTi_GB2312;"> public static void getFieldDemo() throws Exception {
Class clazz = Class.forName("cn.itzixue.bean.Person") ;
//通过Class类的对象的getField("字段名称")方法获取制定类中共有的字段(只能获取共有,可以获取父类)
//通过Class类的对象的getDeclaredField("字段名称")方法获取制定类中共有的字段(只能获取本类,但是可以包含私有)
Field field = clazz.getDeclaredField("age") ;
/*通过获得的字段处理其对应的值
例如:获取字段的值,可用字段对象的get(?)方法,所传入的?必须是该字段所在类的对象
(如果在类中该字段被定义成私有的,则用该方法会抛出无效访问异常)
对私有字段的访问,可以通过其getAccessible(true)方法对其进行暴力访问(不建议!!!)*/
Constructor constructor = clazz.getConstructor(String.class,int.class) ;
Object obj = constructor.newInstance("权权",21) ;
field.setAccessible(true) ;
Object o = field.get(obj) ;
System.out.println(o) ;
//设置所获得的字段的值
field.set(obj, 22);
o = field.get(obj) ;
System.out.println(o);
<span style="font-size:14px;">}</span></span></strong>
通过Class类对象的getField()方法可以获取Class字节码文件中的所有公有的(包括父类)字段;getDeclaredField()方法可以获取Class字节码文件中的所有(仅本类但包括私有的)字段。
五.获取Class中的方法
获取class字节码文件中的方法一般可以通过调用getMethod(String name,Class<?>... parameterTypes)方法,该方法将方法名和其参数同时传入。(如果为空参数,则传入null即可。该方法返回一个Method类对象)。
获取class字节码文件中的方法代码演示:
① (返回值类型:Method[]) methods = Class.getMethods() ; // 获取类(包括父类)中所有的公有方法
② (返回值类型:Method[]) methods= Class.getDeclaredMethods() ; // 获取类中所有方法(包括私有但不包括父类)
③ (返回值类型:Method) method = Class.getMethod(String name, 参数类型.Class,…参数类型.Class) ;
// 返回一个带参数的Method对象,它反映此Class对象所表示的类或接口的指定公共方法
对获取到的Method对象进行调用代码演示:
(返回值类型:Object) invoke(Object obj,参数) ;
// 由obj对象进行方法调用,如果该方法是静态的,则invoke方法中的对象参数可以置为null
代码演示: (注意:注重中包含重要知识点和注意点)
① 获取无参方法
<strong><span style="font-family:KaiTi_GB2312;"><span style="font-size: 14px;"> public static void getMedthodDemo_1() throws Exception {
Class clazz = Class.forName("cn.itzixue.bean.Person") ;
//可以通过Class对象的getMethod()方法来获取类中所有公共方法(包括父类)。
Method[] method_1 = clazz.getMethods() ;
//该方法将返回一个Method数组,可通过foreach遍历
for(Method m : method_1){
System.out.println(m);
}
//可以通过Class对象的getDeclaredMethod()方法来获取类中包括私有在内的所有方法(不包括父类)。
Method[] method_2 = clazz.getDeclaredMethods() ;
//该方法将返回一个Method数组,可通过foreach遍历
for(Method m : method_2){
System.out.println(m);
}
</span><span style="font-size:14px;">}</span></span></strong>
② 获取有参方法并对其进行调用
<strong><span style="font-family:KaiTi_GB2312;"><span style="font-size:14px;"> public static void getMedthodDemo_2() throws Exception {</span>
Class clazz = Class.forName("cn.itzixue.bean.Person") ;
/*获取类中指定的方法,可以通过调用
getMethod(String name,Class<?>... parameterTypes)方法,
该方法将方法名和其参数同时传入。(如果为空参数,则传入null即可)*/
Method method_1 = clazz.getMethod("show", null) ;
Method method_2 = clazz.getMethod("show", String.class) ;
System.out.println(method_1);
System.out.println(method_2);
/*若想调用所取到的Method对象,可以使用Method对象的
invoke(Object obj,Object... args)方法,
该方法需同时传入该类的对象以及相应的参数。*/
Constructor constructor = clazz.getConstructor(String.class,int.class) ;
Object obj = constructor.newInstance("权权",24) ;
method_1.invoke(obj, null) ;
method_2.invoke(obj, "No1") ;
}</span></strong>