目录
首先什么是反射?
我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。反射就像一面镜子,它可以清楚看到类的完整结构信息,可以在运行时动态获取类的信息,创建对象以及调用对象的属性和方法。
那么为什么需要反射呢?
试想一种情景,你想通过配置文件properties的指定信息创建某个对象并执行该对象的私有方法,那如果只是通过new方法是创建不了这个对象的吧,更别说要调用私有方法。
而反射可以很好完成这种实现,这种需求在学习spring框架时也特别频繁,spring是通过读取和解析配置文件从而实现依赖注入和面向切面编程等功能的。反射可以通过外部文件配置,在不修改源码的情况下来控制程序,很好体现了设计模式的OCP原则,即开闭原则:在不修改源码的情况下扩容。
反射机制原理
反射机制允许 Java 程序在运行时调用Reflection API取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作类的实例对象的属性以及方法。
从上图可以看出反射和类加载息息相关。来分析一下上图:
首先编写源代码,然后javac.exe会编译源码为字节码,也就是.class文件,然后字节码文件运行时会进入到类加载阶段,需要用到类加载器(Class Loader)将字节码文件加载到jvm中来,怎么加载的呢?
接着看,加载器通过类的全名获取类的二进制数据流,解析类的二进制数据流为方法区的数据结构,然后在堆中会产生一个Class类型(通过classLoader的loadClass()方法)的对象(一个类只有一个Class对象)来表示该实例,包含了类的完整数据结构(成员变量当作Field[] fields类型,构造器Constructor[] cons ,成员方法Method[]ms ),作为方法区这个类的各种数据的访问入口,这个Class对象像一面镜子,透过这个镜子可以看到类的结构,跟反射一样。
然后在应用时:
1.堆中new对象,这个对象会知道它属于哪个Class对象,就可以直接调用Class对象的东西
2.同时可以得到Class对象,之后可以创建对象,调用对象方法,操作对象属性
分析一下这个Class类:
Class类也是类,继承Object,对于某个类的Class类对象,在内存中只有一份,因为类只加载一次(我debug过,loadClass()方法加载类时,底层有一个synchronized锁,这个是悲观重量级锁,保证同一时刻只能有一个线程执行方法加载类,也就保证了某个类在内存中只有一份Class对象),通过Class类对象可以完整得到一个类的完整数据结构,通过一系列API即可调用类的属性或方法,它是存放在堆里的。
说到这了,再聊聊类加载。
类加载的时机:
1.当创建对象(new)时;2.当子类被加载时,父类也加载;3.调用类中的静态成员;4.通过反射。
前面三种是静态加载,编译时需要加载相关的类,第四种反射是动态加载,只有在运行时需要该类才会加载,走加载类的流程,不需要时,即使该类不存在,也不报错。(反射使得可以不需要在编译时就知道类的信息,对象的类型)
类加载流程:
文件格式验证,比如是否以魔数0xcafebabe开头
那么反射是如何实现获取类对象方法和属性呢?
1.首先获取字节码对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();
2.常用方法
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
一些暴力反射方法,可以获取私有属性和方法