文章目录
一、 前言
哈喽,小白啊里来了~ 我们都知道java反射机制即是重点也算是难点,并不是每个人都很理解。同时,反射机制是java实现动态语言的关键,也是通过反射实现类动态加载,并且框架的底层原理也基于反射。本文啊里将和大家分享自己对反射的理解。
二、通过一个需求引出反射
需求:根据配置文件re.properties指定信息:
classfullpath=com.agli.Cat
method=hi
去创建Cat对象且调用hi方法?
那根据上述需求,你会怎么去解决呢?
传统方式:
1、直接new对象,调用方法这种做法是会不符合OCP原则。(OCP原则即开闭原则:即通过外部文件配置,不修改源码来控制程序。)
2、Properties类读写配置文件:
new Properties().
load(newFileInputStream(“xx\\re.properties”)).
get(“classfullpath”)
使用配置类是能获取属性值,但实现不了需求,会发现不能调用方法。
反射机制:
通过反射机制是可以解决的:
//加载类,返回类对象。
Class cls =Class.forName(“全类名”);
//得到要加载Cat对象
Object o=cls.newInstance();
//获取方法对象
Method mth=cls.getMethod(“方法名”);
//通过对象调用方法
mth.invoke(o);
三、反射
那通过上面那个需求可以发现,只有反射是可以在不修改源码就能完成创建对象和调用方法的。然后有小伙伴就会问了什么是反射呢?能不能说的能理解点呢?
那反射即加载完类后,堆中产生Class类型的对象(一个类只有一个Class对象),此对象包含了类的完整结构信息,然后通过类对象操作类。此对象即类似镜子。
四、反射机制原理
了解了反射的概念之后,我们谈谈它的原理,说到原理这个词可能就有些人会产生很蒙的一种状态,但其实认真去学习还是不难的鸭~
那Java程序在计算机中是有三个阶段的,直接上图:
一:编译阶段:
由源码.java —> 字节码.class
二:Class类阶段(加载阶段):
由类加载器ClassLoader —>(将字节码加载进堆内存中) 产生一个Class类对象 :加载过程即反射的体现
而被加载进来的类成员变量、构造器、成员方法等都被视作对象
三:Runtime运行阶段:
Cat c=new Cat();
c.hi();
Cat对象也会进堆区,且知道他属于哪个Class对象
Ps:当new一个对象时,同时堆区同时进行两步:
先:类加载器加载类对象进堆区(属于jvm底层操作)
后:对应的对象进入堆区
反射机制的原理:即在编译阶段到加载阶段中,类加载的一个过程,经过类加载这一过程,在堆中得到对应的Class对象和方法区中对应的二进制字节流数据。
在反射机制中的得到Class对象后,即可以通过类对象去创建对象、调用方法和操作属性等等。
五、反射的优缺点
凡事都是有两面性的,那反射机制也不例外。
优点:
灵活性:可以动态的创建和使用对象(即是框架底层的核心)。
缺点:
使用反射基本是解释执行,对执行的速度有影响。
对于反射缺点,也自然有优化的方式,这比较重要,面试很有可能会被问到,所以需要注意。
优化:关闭访问检查
通过调用Method、Field和Constructor对象的**setAccessible()**方法去启用或禁用访问安全检查,参数值为true时,取消访问检查,可提高反射效率。
六、类加载
在聊类加载之前我们要知道,反射机制是java实现动态语言的关键,也是通过反射实现类动态加载。
类加载有两种:
- 静态加载:编译时加载相关的类,若没有则报错,依赖性太强。
- 动态加载:运行时加载,若运行时不用该类,则不报错,降低依赖性。
类加载时机:
- new创建对象
- 当子类被加载时,父类也必须加载
- 调用类的静态成员时
- 反射 (除了反射之外,前三都为静态类加载)
类加载流程:
类加载分三个阶段
1.加载(Loading):
将字节码从不同的数据源(可能是class文件、jar包、甚至网络中的字节码)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象。
2.连接(Linking):
2.1验证(verification):
验证文件格式(是否以魔数oxcafebabe开头)、元数据、字节码等
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,
对虚拟机没有危害。
注意:可使用-Xverify:none 参数关闭大部分的类验证措施,
缩短虚拟机类加载时间。
2.2准备(Preparation):
静态变量进行默认初始化并分配内存,所使用的的内存都将在方法区分配。
2.3解析(Resolution):
虚拟机将常量池中的符号引用替换为直接引用。
理解:
此过程中的类没有进入到内存中,不存在地址引用,而是用其他符号代替。
例:A类(符号:#)、B类(符号:%)
当A类里面有引用B类时,不是直接用对象地址引用,而是使用#和%符号去引用。 而转为直接引用,即类加载进内存,有了实际的对象地址。可直接引用对象地址。
3.初始化(initialization):
显示初始化,即源代码里的变量初始化。真正执行java程序代码。
执行clinit()方法:依次自动收集类中所有的静态变量的赋值动作和静态代码块中的语句,并进行合并。
clinit(): jvm会保证一个类的clinit()方法在多线程环境下被加锁且同步,直到一个线程执行完,其他线程都会成阻塞状态。
因此某个类在内存中只会有一个class对象。
注意:1.2阶段由jvm控制,3.是有程序写代码控制
七、反射的认识、使用及注意
Class类对象:
- 也是普通类继承object类。
- 不是通过new出来,而是系统构建,通过ClassLoader的loadclass()方法创建。
- 对于某个类的Class类对象,在内存中只存在一份。
- 每个类的实例都会记录自己属于哪个Class类对象。
- 通过Class类对象的api可获得对应类的完整结构,且Class类对象存放在堆。
- 类的字节码二进制数据(元数据)存放在方法区(eg:方法代码,变量名,方法名,访问权限)。
获取Class类对象方式:
- 编译阶段:
Class.forName()
Ps:多用于框架底层,读取配置文件里的全路径类名获取
-
加载阶段:
类.class
Ps:多用于参数传递
-
运行阶段:
对象.getClass()
Ps:有对象实例时
-
进入加载阶段时:通过类加载器获取
-
基本数据类型:
基本数据类型.class
-
基本数据类型对应的包装类:
包装类.TYPE
反射机制创建实例:
Class class = Class.forName(“xxx”);
1、通过public的无参构造器:
class.newInstance();
2、通过public的有参构造器:
2.1获得有参构造器:Constructor constructor=class.getConstructor(String.class);
2.2创建实例,传入实参:constructor.newInstance(“sss”)
3、通过非public的有参构造器:
1.1 得到private的构造器对象
Constructor constructor=class.getDeclaredConstructor(int.class,String.class);
3.2设置取消访问检查constructor.setAccessible(true)
即暴力破解
反射机制可访问private的构造器、方法、属性
3.3创建实例:constructor.newInstance(100,”dfdd”)
注意:
-
反射机制能生成动态代理。
-
当类加载器加载Class类对象进堆区时,也会将对应类的字节码二进制数据加载进方法区,且堆区的Class类对象被防区的字节码二进制数据引用。
-
哪些类型有Class类对象:外部类(eg:String.class)、接口(eg:Serializab.class)
数组(eg:Integer[].class)、二维数组(eg:float[][].class)、注解(eg:Deprecated.class)
枚举(eg:Thread.State.class)、基本数据类型(eg:int.class)、void数据类型(void.class)
-
类名.静态属性:也会导致类加载过程
八、总结
以上就是小白啊里的分享啦,希望对大家有帮助,多多指教~