💁 个人主页:黄小黄的博客主页
❤️ 支持我:👍 点赞 🌷 收藏 🤘关注
🎏 格言:All miracles start from sometime somewhere, make it right now.
本文来自专栏:JavaSE从入门到精通
文章目录
1 为什么需要反射?
不知道大家有没有思考过一个问题,为什么需要反射?
通过我们以前对Java的学习,我们知道,可以通过 new 的方式,来创建一个对象,并调用对象中的方法。
但是在实际开发中,可以说很多框架都有一个需求:通过配置文件,实现在不修改源码的情况下,实现需求。
那么问题就出现了,如果没有反射机制,我们就没办法通过配置文件来创建对象… …
例子需求如下:
创建一个Cat类,Cat类里包含两个方法,一个为hi,调用会打印hello,另一个方法为say,调用会打印喵喵喵。
请你在main函数中创建Cat类对象,分别调用这两个方法。
Cat类代码如下:
classCat{publicvoidhi(){
System.out.println("hello");}publicvoidsay(){
System.out.println("喵喵喵");}}
常规调用方法如下:
Cat cat =newCat();
cat.say();
显然这样是可行的。
但是,当我们需要调用的方法不是say,而是hi时,就需要修改源码了。
有没有一种方式,能够让我们,仅仅通过外部文件的配置,在不修改源码的情况下,来控制程序呢?
我们尝试 通过Java的反射机制来解决, 相应的源码已经有了注释:
import java.io.FileInputStream;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Properties;/**
* @author 兴趣使然黄小黄
* @version 1.0
* 走进反射
*/publicclassTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InstantiationException,
IllegalAccessException, NoSuchMethodException, InvocationTargetException, IOException {//读取配置文件
Properties properties =newProperties();
properties.load(newFileInputStream("D:\\Ideaproject2021\\JavaSE\\src\\reflection\\re.properties"));
String classFullPath = properties.getProperty("classFullPath");
String method = properties.getProperty("method");//1.加载类,返回Class类型的对象
Class aClass= Class.forName(classFullPath);//2.通过 aClass 得到加载的类 Cat 实例
Object o = aClass.newInstance();
System.out.println("o 的运行类型="+ o.getClass());//3.通过 aClass 得到加载的类 Cat 的 hi 方法对象//在反射中,万物皆是对象,可以把方法也看成对象
Method method1 = aClass.getMethod(method);//4.通过method1调用方法//传统:对象.方法() , 反射机制:方法.invoke(对象)
method1.invoke(o);}}
配置文件信息如下:
运行结果:
如果修改配置文件中method的信息,则可以实现调用不同的方法,我们无需再更改源码。小伙伴可以自己尝试,这里不再赘述!
2 走进Java反射机制
2.1 Java Reflection 说明与原理刨析
反射机制 允许程序在执行期借助于Reflection API取得任何类的内部信息, 比如:类的成员变量、构造器、成员方法等。并且,能够操作对象的属性以及方法。反射在设计模式和框架的底层都经常用到。
加载完类之后,在堆中会产生一个Class类型的对象 (一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过镜子看到类的结构,所以称之为反射。
反射可以做什么?
- 在运行时判断任意一个对象所处的类;
- 在运行时构造任意一个类的对象;
- 在运行时得到任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的成员变量和方法;
- 生成动态代理。
反射机制原理刨析:
Java程序在计算机有三个阶段:编译阶段、加载阶段和运行阶段。
- Student类中包含构造器、成员变量和方法等,其源码通过 Javac 编译,可以得到 Student.class字节码文件;
- Student.class字节码文件中包含了该类的属性、构造器、成员变量、泛型、注解等信息;
- 当我们 new 一个Student对象的时候,会导致类的加载,Student.class字节码文件会被加载,加载到内存的堆区,形成Class类对象;
- 加载通过类加载器实现,形成的Class类对象中包含成员变量、构造器、注解等,体现了反射;
- 在底层,会把成员变量等当作对象来看待,映射成各种数据结构,例如成员变量映射成 Field[] 数组,构造器映射成 Constructor[] 数组;
- 最后生成的对象生成在堆中,该对象知道自身属于哪个Class对象,存在一个映射关系,这就是反射能够操作的原因;
- 当得到Class对象后,可以创建对象、调用对象方法、操作属性等。
2.2 反射的相关类
在反射中,我们需要掌握的主要类如下:
java.lang.Class
:代表一个类,Class对象表示某个类加载后在堆中的对象;java.lang.reflect.Method
:代表类的方法,Method对象表示某个类的方法;java.lang.reflect.Field
:代表类的成员变量,Field对象表示某个类的成员变量;java.lang.reflect.Constructor
:代表类的构造方法,Constructor对象标识构造器;
在下面的例子中展示反射的使用:
//获取成员变量//getField不能得到私有属性
Field field1 = aClass.getField(field);
System.out.println(field1.get(o));//获取无参构造方法
Constructor c1 = aClass.getConstructor();//获取有参构造方法
Constructor c2 = aClass.getConstructor(String.class);//传入参赛的class对象
2.3 反射调用优化
反射可以动态的创建和使用对象,但是,使用反射基本是解释执行,对执行速度有一定的影响。
下面通过一个例子来让大家体验一下使用反射和不使用反射,效率上的区别,然后我们再来谈谈如何优化(虽然作用微乎其微)。
import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/**
* @author 兴趣使然黄小黄
* @version 1.0
* 反射机制使用与否的效率比对
*/publicclassReflectionTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InvocationTargetException,
InstantiationException, IllegalAccessException, NoSuchMethodException {m1();m2();}//使用传统方法publicstaticvoidm1(){
Cat cat =newCat();long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
cat.hi();}long end = System.currentTimeMillis();
System.out.println("传统方式调用 m1 耗时: "+(end-start)+"ms");}//使用反射方法publicstaticvoidm2()throws ClassNotFoundException, InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
Class aClass= Class.forName("reflection.Cat");
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi");long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
hi.invoke(o);}long end = System.currentTimeMillis();
System.out.println("使用反射调用 m2 耗时: "+(end-start)+"ms");}}
可以看到,使用反射与否,对执行效率是有一定影响的。
如何对反射调用进行优化呢?
可以通过 关闭访问检查 的方式来优化:
- Method、Field和Constructor对象都有setAccessible()方法;
- setAccessible()作用是启动和关闭访问检查的开关;
- 参数值为true表示,反射的对象在使用时取消访问检查,提高反射效率。而参数值为false,则表示反射的对象执行访问检查。
import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/**
* @author 兴趣使然黄小黄
* @version 1.0
* 反射机制使用与否的效率比对
*/publicclassReflectionTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InvocationTargetException,
InstantiationException, IllegalAccessException, NoSuchMethodException {m1();m2();m3();}//使用传统方法publicstaticvoidm1(){
Cat cat =newCat();long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
cat.hi();}long end = System.currentTimeMillis();
System.out.println("传统方式调用 m1 耗时: "+(end-start)+"ms");}//使用反射方法publicstaticvoidm2()throws ClassNotFoundException, InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
Class aClass= Class.forName("reflection.Cat");
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi");long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
hi.invoke(o);}long end = System.currentTimeMillis();
System.out.println("使用反射调用 m2 耗时: "+(end-start)+"ms");}//使用反射方法,进行过优化publicstaticvoidm3()throws ClassNotFoundException, InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
Class aClass= Class.forName("reflection.Cat");
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi");//关闭反射调用方法时的访问检查
hi.setAccessible(true);long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
hi.invoke(o);}long end = System.currentTimeMillis();
System.out.println("反射优化调用 m3 耗时: "+(end-start)+"ms");}}
虽然作用是很小的,但是优化后的m3相较m2执行效率已经有了一些改善。
3 Class类
3.1 Class类说明与演示
Class类的基本说明:
- Class也是一个类,是Object类的子类;
- Class类对象不是new出来的,是由系统创建的;
- 对于某个类的Class对象,在内存中只有一份,因为类只加载一次;
- 每个类的实例都会记得自己是哪个Class实例所生成的;
- 通过Class可以完整地得到一个类的完整结构,通过一系列API;
- Class对象存放在堆中;
- 类的字节码二进制数据,是放在方法区的,也有时候叫做类的元数据(包括方法代码、变量名、方法名、访问权限等)。
3.2 Class常用方法
方法摘要:
演示准备:Car类
/**
* @author 兴趣使然黄小黄
* @version 1.0
*/publicclassCar{public String brand ="奥迪";publicint price =999999;public String color ="金属黑";@Overridepublic String toString(){return"Car{"+"brand='"+ brand +'\''+", price="+ price +", color='"+ color +'\''+'}';}}
下面对Class常用方法的使用进行演示:
import java.lang.reflect.Field;/**
* @author 兴趣使然黄小黄
* @version 1.0
* 演示Class类的常用方法
*/publicclassClassTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classAllPath ="reflection.Car";//1.获取Car类对应的Class对象//<?>表示不确定的Java类型
Class<?> cls = Class.forName(classAllPath);
System.out.println("cls = "+ cls);//显示cls对象,是哪个类的Class对象
System.out.println("cls 的运行类型 = "+ cls.getClass());//输出运行类型//2.获取包名
System.out.println("包名 = "+ cls.getPackage().getName());//3.得到全类名
System.out.println("全类名 = "+ cls.getName());//4.生成对象实例
Car car =(Car) cls.newInstance();
System.out.println(car);//会调用car.toString()//5.通过反射获取属性,但是无法获得私有属性
Field brand = cls.getField("brand");
System.out.println("brand的值 = "+ brand.get(car));//6.通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println("修改后brand的值 = "+ brand.get(car));//7.通过遍历,得到Car类中的所有属性
System.out.println("===================字段名==================");
Field[] fields = cls.getFields();for(Field f :
fields){
System.out.println(f.getName());}}}
3.3 获得Class对象的方式
回顾:Java程序在计算机中有三个阶段,分别是 编译阶段、加载阶段和运行阶段。
四种核心拿到Class对象的方式如下:
- 编译阶段:
Class.forName();
- 加载阶段:class对象已经存在了,可以通过
类.class()
拿到; - 运行阶段:对象已经存在,可以通过
对象.getClass()
拿到; - 类加载器:通过
类加载器
拿到Class对象。
下面 根据不同的应用场景,来演示获取Class对象的方式:
1️⃣ 多用于配置文件,读取全路径,加载类。
前提: 已经知道一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
实例:
Class cls= Class.forName("xxx.xxx.xxx");
2️⃣ 多用于参数传递,比如通过反射得到对应的构造器对象。
前提: 若已知具体的类,通过Class获取,该方式最为安全可靠,程序性能高。
实例:
Class cls2= XXX.class;
3️⃣ 通过创建好的对象,获取Class对象
前提: 已知某个类的实例,调用该实例的getClass()方法获取Class对象。
实例:
Car car =newCar();
Class<?extendsCar> cls3 = car.getClass();
4️⃣ 通过类加载器。
实例:
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();//(2)通过类加载器得到Class对象
Class<?> cls4 = classLoader.loadClass(classAllPath);
比较四种方式对象是否为同一对象
/**
* @author 兴趣使然黄小黄
* @version 1.0
* 获取Class的不同方式,演示
*/publicclassGetClassTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException {//1. Class.forName
String classAllPath ="reflection.Car";
Class<?> cls1 = Class.forName(classAllPath);
System.out.println("cls1 = "+ cls1);//2.类名.class,多用于参数传递
Class<Car> cls2 = Car.class;
System.out.println("cls2 = "+ cls2);//3.对象.getClass(),已经知道有对象实例
Car car =newCar();
Class<?extendsCar> cls3 = car.getClass();
System.out.println("cls3 = "+ cls3);//4.通过类加载器来获取Class对象//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();//(2)通过类加载器得到Class对象
Class<?> cls4 = classLoader.loadClass(classAllPath);
System.out.println("cls4 = "+ cls4);//比较
System.out.println("是否为同一对象:"+((cls1.hashCode()==cls2.hashCode())&&(cls3.hashCode()==cls4.hashCode())&&(cls2.hashCode()== cls3.hashCode())));}}
当然除了以上四种方式,还有一些琐碎的其他方式没有叙述
5️⃣ 基本数据类型获取Class类对象的方式
Class cls= 基本数据类型.class;
6️⃣ 基本数据类型对应的包装类,可以通过.type得到Class类对象
Class cls= 包装类.TYPE;
3.4 哪些类型有Class对象
- 外部类,内部成员类,静态内部类,局部内部类,匿名内部类;
- interface:接口;
- 数组;
- enum:枚举;
- annotation:注解;
- 基本数据类型;
- void。
写在最后
🌟以上便是本文的全部内容啦,后续内容将会持续免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
如果有问题,欢迎私信或者评论区!
下一节,将会与大家聊聊 类加载与反射爆破,欢迎关注!
共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”
版权归原作者 兴趣使然黄小黄 所有, 如有侵权,请联系我们删除。