引言
研究过框架源码的朋友们应该知道,没有反射,就没有框架。反射的功能实在是太强大了,所以这篇博客就来总结一下反射的用法和原理。
一、概念:
反射:将类的各个组成部分封装成其他对象,通过这些对象反过来作用类的实例对象,这就是反射机制。
可能听不懂了,啥啥啥呀?别着急,先来看一下一个Java类从编译到运行都经历过那些阶段吧:
首先,程序以Java代码的方式是无法运行的,一个程序要被运行,需要被编译成class文件。类加载器会加载class文件,并在JVM的方法区中为类创建类对象(注意类对象是保存在方法区的,这与类的实例对象保存在堆中有所区别),那么方法区中的这个类对象时如何封装的呢?我们知道:Java中一切皆为对象。在封装类对象时,也会将类的成员封装成对应的对象、也就是说,类的成员变量会被封装成Field对象,并创建一个数组保存这些对象。也会为所有的方法封装Method对象,并创建一个Method[]数组保存它们。也会为所有的构造器封装Constructor对象,并创建Constructor[]数组保存它们,所以:将类的各个组成部分封装成其他对象。说的就是这个意思。
二、使用:
知道了JVM会为类以及类的成员封装对象,那么如何获取这些对象呢?
1、获取类对象(Class对象):
①、当类没有被加载时:使用Class.forName("全类名"),将字节码文件加载到内存,返回一个Class对象。这种方法很常见,加载数据库驱动使用的就是这个方法。
此方法多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
②、当类已经被加载时:使用类名.class,也可以返回类对象。
此方法多用于参数传递:将类作为参数传递,但要区别于泛型。泛型虽然是参数化类型,但是并不会向方法里的参数那样,必须传值。
③、使用类的实例对象:实例对象.getClass(),也可以返回类对象。
多用于对象的获取字节码的方式。
此外,由于类一旦被加载到内存,并在方法区中创建类对象以后,就不会再次加载该类。并且在方法区中只会为一个字节码文件封装一个类对象,所以通过以上三种方式获得的Class对象其实是一个对象。
2、使用Class对象后去成员对象:
既然拿到Class对象,自然就可以获取到类的成员所封装成的对象了。
①、获取成员变量们:
Field getFields() :获取所有public修饰的成员变量
Field getFiled(String name) :获取指定名称的public修饰的成员变量
Filed[] getDeclaredFields() :获取所有的成员变量,忽略修饰符
Field getDeclaredField(String name) :获取指定的成员变量,忽略修饰符
②、获取构造方法们:
Constructor<?>[] getConstructors() :获取所有public修饰的构造器
Constructor<T> getConstructor(类<?>...parameterTypes) :获取指定参数类型的public修还是的构造器
Constructor<T> getDeclaredConstructor(类<?>...parameterTypes) :获取指定参数类型的构造器,忽略修饰符
Constructor<?>[] getDeclaredConstructors() :获取所有的构造器,忽略修饰符
③、获取成员方法们:
Method[] getMethods() :获取public修饰的方法
Method getMethod(String name,类<?>...parameterTypes) :获取指定名次和参数列表的public方法
Method[] getDeclaredMethods() :获取所有的方法,忽略修饰符
Method getDeclaredMethod(String name,类<?>...parameterTypes) :获取指定名称和参数列表的方法,忽略修饰符
④、获取类名:
String getName() :获取类的名字
3、使用成员对象:
以上图中的Person类的name属性为例:
通过Class p = Person.class
Filed name = p.getDeclaredField("name");
Person person = new Person("xiaowang");
name.setAccessible(true);使得私有对象可以被设值
name.get(person);//输出:xiaowang
name.set(person,"zhangsan");
name.get(person);//输出:zhangsan
这就是反射的思想,以前我们是通过对象设置和对象的属性值唯一确定一个变量为其赋值,而反射则是通过变量封装成的对象的方法,通过传入指定对象,为指定对象的此属性设置值。通过反射调用方法和构造器的原理也是如此。
①、使用成员变量通过反射作用到具体的实例对象
操作:
设置值:void set(Object obj, Object value); obj为具体要操作的实例对象,如:name.set(person,"zhangsan");
获取值:get(Object obj); :name.get(person);
忽略访问控制权限:setAccessible(true);
②、使用构造方法对象创建实例对象
T newInstance(Object... initargs) :如constructor1.newInstance("zhangsan",23);
如果使用空参构造器创建对象,可以简化为:Class对象直接调用newInstance方法,效果等价于,空参构造器封装成的实例对象constructor.newIastance();
①、使用成员方法通过反射调用实例对象的对应方法
执行方法:Object invoke(Object obj, Object... args),obj为具体要调用方法所属的实例对象,args为方法的参数列表,通过这种方式唯一确定调用哪一个具体实例对象的具体方法。
获取方法名称:String getName() method.getName()
了解反射机制将有助于了解大多数框架的设计思维,也是设计自己的框架必须灵活使用的知识点。