前言
我们在日常的开发中其实很少写反射,只知道有这么个东西,然而对于为什么用它却知之甚少。实际上,我们的框架中大量用到了这种概念。例如,当我们需要获取某个包下的类,并进行统一处理时,我们就需要知道这些类的名称,通过new 创建实例对象;但是如果我们不知道类有哪些,或者说我们只是在最底层封装的代码,对于用户定义的类名称根本不知道,这个时候,反射就起到作用了。下面让我们一起学习一下Java的动态获取的信息以及动态调用对象的方法的反射机制吧。
一、反射概述
反射: 反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)。如果从上面的反射的定义上来看,反射是什么不太容易理解。通俗的讲,我们日常开发中使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
Apple apple = new Apple(); //直接初始化 apple.setPrice(4);
上面这样子进行类对象的初始化,我们可以理解为“正射”。而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用:
Class clz = Class.forName("com.chenshuyi.reflect.Apple"); Method method = clz.getMethod("setPrice", int.class); Constructor constructor = clz.getConstructor(); Object object = constructor.newInstance(); method.invoke(object, 4);
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。
所以说什么是反射?反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
1.1 反射过程
* 反射过程描述: 首先会有一个java文件,然后会编译成.class文件,字节码文件(会里面包含了当前这个类里面所有数据信息)会加载到内存, JVM负责管理,为了方便管理,它会将每一个加载进来的class文件生成一个对应的Class对象<对应的类型就是生成字节码之前java文件 对应的类名类型>,这个class对象它就代表对应的类(java文件),那么我们可以通过这个类对应的class对象获得这个类中的数据信息 (成员变量、成员方法、构造方法)
结论:我们使用反射,其实就是获得某个类的Class对象,然后通过这个Class对象对这个类中的成员数据进行处理(调用执行)!
1.2 反射前提
我们必须得到这类的字节码文件(.class文件) ====>>> Class对象
Class:它是一个类,Class类的实例表示正在运行的Java应用程序中的类和接口。我们的一个类最终在内存中都是字节码,然后JVM会对每一个类对应的字节码生成一个Class对象!然后通过这个Class对象就可以访问类中的所有信息!
* Java类与Class对象的对应关系: 一个类 =======>>> 一个Class对象 类中的一个成员变量 ======>>>> 一个Field对象(在程序运行阶段为类中的成员变量赋值或者获取值) 类中的一个成员方法 ======>>>> 一个Method对象(在程序运行阶段执行类中的成员方法) 类中的一个构造方法 ======>>>> 一个Constructor对象(在程序运行阶段创建当前类的一个实例对象)
二、 如何获得类对应的Class对象
2.1 方式一:通过Class类的静态方法
// 参数:某个类的全限定类名(包含包名和类名) static Class<?> forName(String className) // 返回与给定字符串名称的类或接口相关联的Class对象。
Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); // 给定类的全限定类名书写错误,会导致异常!ClassNotFoundException
2.2 方式二:通过类的对象的getClass方法
// 先获得到某个类的对象 Student student = new Student(); // 通过对象调用方法获得Class对象! Class<? extends Student> clazz1 = student.getClass(); // getClass()方法是从Object继承过来的
2.3 方式三:通过类名的静态属性class
// 通过类名.class获得Class对象! Class<Student> clazz = Student.class;
2.4 Class对象的常用功能
* String getSimpleName(); // 获得类名字符串:类名 * String getName(); // 获得类全名:包名+类名 * T newInstance() ; // 创建Class对象关联类的对象(公有构造方法)
public static void main(String[] args) throws Exception{ // 获得Student类对应的Class对象 Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); System.out.println(clazz.getSimpleName()); // Student System.out.println(clazz.getName()); // com.itheima.demo01_反射.Student Object obj = clazz.newInstance(); // com.itheima.demo01_反射.Student@1647C }
三、通过Class对象操作构造方法
3.1 获得Constructor对象
Constructor<?>[] getConstructors() // 通过Class对象获得类中所有的公有的构造方法对象 Constructor<?>[] getDeclaredConstructors() // 通过Class对象获得类中所有的构造方法对象(公有私有,全都要) Constructor<T> getConstructor(Class<?>... parameterTypes) // 通过Class对象获得类中指定公有的构造方法对象! 【★★★★★】 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 通过Class对象获得类中指定的构造方法!(公有私有,全都要)
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { // 获得Student类对应的Class对象 Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); /////////////////////通过Class对象获得Student类中构造方法对应的Constructor对象!/////////////////// // 获得类中所有的公共的构造方法对应的对象数组 Constructor<?>[] constructors = clazz.getConstructors(); System.out.println(Arrays.toString(constructors)); // // 获得类中所有的构造方法对应的对象数组(包括私有的!) Constructor<?>[] dc = clazz.getDeclaredConstructors(); System.out.println(Arrays.toString(dc)); // 获得类中指定构造方法对应的对象 //Constructor<?> c1 = clazz.getConstructor(); Constructor<?> c1 = clazz.getDeclaredConstructor(); System.out.println(c1); Constructor<?> c2 = clazz.getDeclaredConstructor(String.class, int.class); System.out.println(c2); }
3.2 通过构造方法对象创建类的实例对象
T newInstance(Object... initargs) 使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
public static void main(String[] args) throws Exception{ // 获得Student类对应的Class对象 Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); // 获得Student类中指定的构造方法对象! Constructor<?> constructor = clazz.getConstructor(); // 根据构造方法对象创建Student类的实例对象! Object o = constructor.newInstance(); System.out.println(o); // com.itheima.demo01_反射.Student@14ae5a5 Student s = (Student) o; }
注意:一旦类中的构造方法使用private修饰,那么通过构造方法对象创建了的实例对象就要发生变化!
public static void main(String[] args) throws Exception{ // 获得Student类对应的Class对象 Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); // 获得Student类中指定的构造方法对象! Constructor<?> constructor = clazz.getDeclaredConstructor(); // 有变动! // 暴力访问! constructor.setAccessible(true); // 有变动! // 根据构造方法对象创建Student类的实例对象! Object o = constructor.newInstance(); System.out.println(o); // com.itheima.demo01_反射.Student@14ae5a5 Student s = (Student) o; }
四、 通过Class对象操作成员方法对象
4.1 获得成员方法的Method对象
Method getMethod(String name, Class<?>... parameterTypes) // 返回指定公有的方法对象! 【★★★★★】 Method[] getMethods() // 返回所有公有的方法对象构成的数组,包括由类或接口声明的对象以及从超类和超级接口继承的类。 Method getDeclaredMethod(String name, Class<?>... parameterTypes)// 返回指定的方法对象!(私有的也可以玩) Method[] getDeclaredMethods() // 返回所有的方法对象构成的数组,包括public,protected,default(package)访问和私有方法,但不包括继承方法。
public static void main(String[] args) throws Exception{ // 获得Student类对应的Class对象 Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); // 获得Student类以及父类(接口)中所有的公有成员方法 Method[] methods1 = clazz.getMethods(); for (Method method : methods1) { System.out.println(method); } System.out.println("===================================="); // 获得Student类中所有的成员方法(包含私有) Method[] methods2 = clazz.getDeclaredMethods(); for (Method method : methods2) { System.out.println(method); } System.out.println("===================================="); // 获得指定方法(公有无参) Method show1 = clazz.getMethod("show1"); // 获得指定方法(私有无参) Method show2 = clazz.getDeclaredMethod("show2"); // 获得指定方法(公有带参数) Method method1 = clazz.getMethod("method1", String.class); // 创建类的对象 Object obj = clazz.getConstructor().newInstance(); // 获得成员方法对象,期目的是为了执行这个成员方法 Object result = show1.invoke(obj);// 参数1:当前方法所属对象! 参数2:show1方法的参数值(若定义show1方法没有参数,这里就不写内容) System.out.println(result); // null [show1方法定义的时候没有返回值] System.out.println("======================"); // 执行带有参数的公有成员方法 Object o = method1.invoke(obj, "666"); System.out.println(o); }
4.2 通过Method对象执行指定方法
/* 参数obj:当前Method对象所属的类的对象!【实际的对象值!】 参数args:为方法对象的形参赋值(若方法对象对应的方法定义没有参数,这里可以空着不写<这里是一个可变参数>) 返回值Object:执行完当前方法的返回结果! */ Object invoke(Object obj, Object... args) // 在具有指定参数的指定对象上调用此 方法对象表示的基础方法。 【★★★★★】
public static void main(String[] args) throws Exception{ // 获得Student类对应的Class对象 Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); // 创建类的对象 Object obj = clazz.getConstructor().newInstance(); // 获得指定方法(公有无参) Method show1 = clazz.getMethod("show1"); // 获得指定方法(公有带参数) Method method1 = clazz.getMethod("method1", String.class); // 获得成员方法对象,期目的是为了执行这个成员方法 Object result = show1.invoke(obj);// 参数1:当前方法所属对象! 参数2:show1方法的参数值(若定义show1方法没有参数,这里就不写内容) System.out.println(result); // null [show1方法定义的时候没有返回值] System.out.println("======================"); // 执行带有参数的公有成员方法 Object o = method1.invoke(obj, "666"); System.out.println(o); }
五、 通过Class对象操作成员变量
5.1 获得成员变量的Field对象
public static void main(String[] args) throws Exception{ // 获得Student类对应的Class对象 Class<?> clazz = Student.class; // 获得类的实例 //Object obj = clazz.newInstance(); Student s = (Student) clazz.newInstance(); // 获得公有成员变量对应的Field对象(很多) Field[] fields = clazz.getFields(); System.out.println(Arrays.toString(fields)); // 获得指定公有成员变量 Field name = clazz.getField("name"); System.out.println(name); // 获得所有的成员变量对应的Field对象(公有私有均可) Field[] declaredFields = clazz.getDeclaredFields(); System.out.println(Arrays.toString(declaredFields)); // 获得指定成员变量(公有私有均可) Field age = clazz.getDeclaredField("age"); System.out.println(age); }
5.2 通过Field对象获取成员变量的值或者设置值
public static void main(String[] args) throws Exception{ // 获得Student类对应的Class对象 Class<?> clazz = Student.class; // 获得类的实例 //Object obj = clazz.newInstance(); Student s = (Student) clazz.newInstance(); /////////////////////////////////操作公有的成员变量//////////////////////// // 获得指定公有成员变量 Field name = clazz.getField("name"); // 为公有的成员变量设置值 System.out.println(s.getName()); // null name.set(s,"jack"); // 获得指定公有成员变量的值 System.out.println(name.get(s)); // jack System.out.println(s.getName()); // jack /////////////////////////////////操作私有的成员变量//////////////////////// // 获得指定成员变量(公有私有均可) Field age = clazz.getDeclaredField("age"); // 暴力访问 age.setAccessible(true); // 获取值 System.out.println(age.get(s)); // 0 // 设置值 age.set(s,38); System.out.println(age.get(s)); // 38 }
6 后记
开发过程中 我们不会通过反射的机制来获取对象,主要是因为反射的代码阅读性不好,不易理解。其次,不易于代码维护。我们会在系统架构时将其用于底层代码封装或者自定义工具类,除此之外,反射在开发中基本很少用到!