反射:框架设计的灵魂
1、概念
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
框架:半成品软件,可以在框架的基础上进行软件开发,简化编码
反射:将类的各个组成部分封装成其他对象(Field,Constructor,Method),这就是反射机制
好处(见第四点)
可以再程序运行过程中,操作这些对象
可以解耦,提高程序的可扩展性
看到这里是不是一脸懵逼,放心看到最后就会豁然开朗的!
2、获取Class对象的方式
什么是Class类?
Class类是一个特殊类,它用于表示JVM运行时类或接口的信息。一个类只有被JVM加载后才能使用,当类被虚拟机加载后都会在内存中创建一个该类的Class对象,用于存储该类的各种信息。
什么是Class对象?
在Java里万物皆对象,那么每个类除了有自己的对象,这些类其实也都是Class类的对象
Class对象的由来
磁盘中的字节码文件是无法直接使用的,需要JVM内存中的才可以使用,而加载类时就是在本地磁盘中找到这个类的字节码文件将其加载到JVM内存中并生成对应的Class对象
反射机制允许程序在运行时取得任意一个类的内部信息,那么是怎样获得其信息的呢,就需要我们首先先得到这个类对应的字节码对象,即Class对象。而Class类的构造方法属于私有方法,故无法直接使用new来生成对象,而是使用以下三种方法。
Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
多用于配置文件,将类名定义在配置文件中,读取文件来加载类
类名.class():通过类名的属性class获取
多用于参数的传递
对象.getClass():getClass()方法在Object类中定义着
多用于对象的获取字节码的方式常用的是第一种方法
以下为各种方式获取Class对象的示例
package com.reflect; import com.domain.Person; public class ReflectDemo1 { public static void main(String[] args) throws Exception { Class cls = Class.forName("com.domain.Person"); System.out.println(cls); Class cls2 = Person.class; System.out.println(cls2); Person p = new Person(); Class cls3 = p.getClass(); System.out.println(cls3); System.out.println(cls == cls2); // true System.out.println(cls == cls3); // true } }
结论
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
3、Class对象功能
Class对象存储着该类的各种信息,我们可以通过Class类的各种get方法来获取原本类的变量,构造方法以及成员方法等信息,获取方式如下
获取成员变量们
Field[ ] getFields( ):获取所有public修饰的成员变量
Field getField(String name):获取指定名称的public修饰的成员变量
Field[ ] getDeclaredFields( ):获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name)
获取构造方法们
Constructor<?>[ ] getConstructors( ):获取所有public修饰的构造函数
Constructor<?> getConstructor(类<?>… parameterTypes)
Constructor<?>[ ] getDeclaredConstructors( ):获取所有构造函数,不考虑修饰符
Constructor<?> getDeclaredConstructor(类<?>… parameterTypes)
获取成员方法们
Method[ ] getMethods( ):获取所有public方法(包括父类的方法)
Method getMethod(类<?>… parameterTypes)
Method[ ] getDeclaredMethods( ):获取所有方法(不包括父类的方法)
Method getDeclaredMethod(类<?>… parameterTypes)
获取类名
String getName( ):获取完整类名
获取到类的各个组成部分封装出来的对象之后,就可以通过这个对象来操作类了
Field:成员变量
- 设置值:void set(Object obj, Object value)
- 获取值:Object get(Object obj)
- 忽略访问权限修饰符的安全检查:seeAccessible(true)——暴力反射
package com.reflect; import com.domain.Person; import java.lang.reflect.Field; public class ReflectDemo2 { public static void main(String[] args) throws Exception { // 获取person的class对象 Class cls = Person.class; // 获取所有public修饰的成员变量 Field[] fields = cls.getFields(); for(Field field : fields){ System.out.println(field); } // 获取指定名的成员变量 Field field = cls.getField("height"); Person p = new Person(); // 获取p对象中名为height的成员变量的值 Object value = field.get(p); System.out.println(value); // 因为是没有设置数据的整形,故默认为0 // 为p对象的height成员变量设置值 field.set(p,150); System.out.println(p); Field field2 = cls.getDeclaredField("name"); // 获取private成员变量 field2.setAccessible(true); // 获取或设置非public的成员前需要暴力反射,忽略访问权限修饰符的安全检查 field2.set(p,"cyh"); // 为name成员变量设置值 Object value2 = field2.get(p); System.out.println(value2); } }
Constructor:构造方法
- 创建对象:T newInstance(Object… initargs)
- 如果使用空参数构造方法创建对象,操作可以简化:使用Class对象的newInstance()方法
package com.reflect; import com.domain.Person; import java.lang.reflect.Constructor; public class ReflectDemo3 { public static void main(String[] args) throws Exception { Class personClass = Person.class; // 获取含三个参数的构造函数 Constructor constructor = personClass.getConstructor(String.class,int.class,int.class); // 根据这个构造函数创建一个对象 Object person1 = constructor.newInstance("cyh",19,170); System.out.println(person1); // 获取无参构造函数 Constructor constructor1 = personClass.getConstructor(); // 无参创建一个对象 Object person2 = constructor1.newInstance(); System.out.println(person2); // 简化无参构造创建对象的方法 Object person3 = personClass.newInstance();// 此法已被抛弃 } }
Method:方法对象
- 执行方法:Object invoke(Object obj, Object… args)
- 获取方法名称:String getName:获取方法名
package com.reflect; import com.domain.Person; import java.lang.reflect.Method; public class ReflectDemo4 { public static void main(String[] args) throws Exception{ Class personClass = Person.class; // 获取person类中的所有公有方法(包括父类的方法) Method[] methods = personClass.getMethods(); for(Method method : methods){ System.out.println(method); System.out.println(method.getName()); } System.out.println("======================"); // 获取person类中的所有方法(不包括父类的方法) Method[] methods1 = personClass.getDeclaredMethods(); for(Method method : methods1){ System.out.println(method); System.out.println(method.getName()); } // 获取方法 Method method = personClass.getMethod("add",int.class,int.class); // 执行方法 Person p = new Person(); System.out.println(method.invoke(p,2,3)); // 忽略访问权限修饰符的安全检查 // method.setAccessible(true); // 获取类名 System.out.println(personClass.getName()); } }
4、案例
经过上述对反射的阐述以及上图的对比我们可能会想,反射到底有什么用?
我们直接new一个对象就可以实现的工作为什么要绕一大圈获取Class对象再来实现呢?
反射的存在自然有其作用,那就是灵活!
下面例子也许帮助我们初窥其妙用
需求:写一个“框架”,在不改变该类的任何代码的前提下,可以帮我们创建任何类的对象,并且执行其中的方法
实现:
配置文件
反射
步骤:
将需要创建的对象的全类名和需要执行的方法定义在配置文件中
再程序中加载读取配置文件
使用反射技术来加载文件进内存
创建对象
- 执行方法
package com.reflect; // 不用导入类包 import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; public class ReflectTest { public static void main(String[] args) throws Exception { // 1.加载配置文件 // 1.1创建properties对象 Properties pro = new Properties(); // 1.2加载配置文件,转换为一个集合 // 1.2.1加载class目录下的配置文件 ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("pro.properties"); pro.load(is); // 2.加载配置文件中定义的数据 String className = pro.getProperty("className"); String methodName = pro.getProperty("methodName"); // 3.加载该类到内存 Class cls = Class.forName(className); // 4.创建该类的对象 // Object obj = cls.newInstance(); Constructor constructor = cls.getConstructor(); Object obj = constructor.newInstance(); // 5.获取方法对象 Method method = cls.getMethod(methodName); // 6.执行方法 method.invoke(obj); } }
properties className=com.domain.Person methodName=eat
以上例子实则就是在配置文件中写下类名,通过反射获得到该类的Class对象进而可以得到这个类的信息或调用其方法
这个时候如果我们不用反射,而是直接导入类,通过new来生成实例的话,那么显然我们每次修改类的方法时,都需要再为这个类写一段调用不同方法的代码,或者使用不同类时,都需要重新写一段创建对象并使 用方法的代码,如下
// 使用Person类时 package com.reflect; // 导入Person的包 import com.domain.Person; public class ReflectTest { public static void main(String[] args) throws Exception { Person p = new Person(); p.eat(); } }
// 使用Student类时 package com.reflect; // 导入Student的包 import com.domain.Student; public class ReflectTest { public static void main(String[] args) throws Exception { // 重写代码以创建对象并使用对象的方法 Student stu = new Student(); stu.sleep(); } }
但是我们并不希望修改代码,因为修改代码的风险更大,且需要重新进行编译,编写测试等工作(虽然这些在以上这个极其简单的例子难以体现),而即使我们不知道代码的具体实现,也能够修改配置来完成我们想实现的,如果存在变动,那么我们在配置里边写,以后无论什么信息修改了,我都能够通过修改配置的方式去实现,这不就可以提高程序的灵活性了吗?如果使用反射,我们只用将修改配置文件为如下便完成工作,是不是非常方便灵活!
className=com.domain.Student methodName=sleep