Java面试重点_4. 一文彻底搞懂Java中的反射 0.5
文章目录
一, 什么是反射? 反射存在的意义是什么?
- 反射作为Java这门语言中, 可以说是最抽象的一种概念, 让我们先从知乎大佬的一番近似于大白话的叙述看起来:
- 概念:
Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。
1.1 反射存在的意义
有的同学可能会疑惑,Java 已经有了封装为什么还要有反射呢?反射看起来像是破坏了封装性。甚至让私有变量都可以被外部访问到,使得类变得不那么安全了。从 Oracle 官方文档中可以看出,反射主要应用在以下几方面:
- 反射让开发人员可以通过外部类的全路径名创建对象(这种也被说法也等同于 在运行时才知道要操作的具体类),并使用这些类,实现一些扩展的功能。
- 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
- 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。
也就是说,Oracle 希望开发者将反射作为一个工具,用来帮助程序员实现本不可能实现的功能(perform operations which would otherwise be impossible)。正如《人月神话》一书中所言:软件工程没有银弹。很多程序架构,尤其是三方框架,无法保证自己的封装是完美的。如果没有反射,对于外部类的私有成员,我们将一筹莫展,所以我们有了反射这一后门,为程序设计提供了更大的灵活性。工具本身并没有错,关键在于如何正确地使用。
二, 反射是如何体现了动态性的?
- Java 是一种静态语言, 借助反射, 他可以成为一种半动态的语言, 那么反射是如何体现出动态性的呢?
先来看个代码🌰:
总结:
三, 取得Class对象的六种方法
- 我们知道, 在装有源代码的.java文件被.javac编译器编译之后, 形成了.class文件, 然后这个.class文件将会被交给JVM, 由JVM把他解释为机器能够识别的机器码;
- 在这个解释运行的过程中, .class文件会先被类加载器(classLoader)加载到方法区(特殊的堆), 针对每个.class文件都会生成一个唯一的Class类对象, 利用这个Class类对象, 我们可以从这个.class文件中获取到这个类的各种信息(变量, 方法, 构造器等等)
- 在创建对象实例(即 new一个对象)之前,JVM会先检查Class对象是否在内存中存在,如果不存在,则加载Class对象,然后再创建对象实例,如果存在,则直接根据Class对象创建对象实例。
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。
这种通过Class实例获取class信息的方法称为反射(Reflection)。 哎, 圆上了吧;
获取Class类对象的前四种方法
- 通过调用被反射类.class, 被反射类.class ,
- 通过使用被反射类的对象, 调用getClass(), 被反射类对象.getClass ,
- 通过调用Class的静态方法, Class.forName(“全类名”)
- 通过使用类的加载器, 被反射类.getClassLoder().loadClass(“全类名”)
package cn.cyy.cl.common.bean;
/**
方法一不执行静态块和动态构造块, 推荐使用;
方法二执行静态块、不执行动态构造块, 这种方法可能需要导入包依赖;
方法三需要创建对象,静态块和动态构造块均会执行, 这种方法属于脱裤子放屁型的, 既然都自己创建对象了, 还获取Class类对象干嘛
*/
public class Dog {
public static void main(String[] args) throws ClassNotFoundException {
//获取Class对象的四种方式
//1. 通过Class.forName("全类名");
Class clazz1 = Class.forName("cn.cyy.cl.common.bean.Dog");
//2. 通过运行时类.class
Class clazz2 = Dog.class;
//3. 通过运行时对象.getClass();
Dog dog = new Dog();
Class clazz3 = dog.getClass();
//4. 通过类加载器
ClassLoader classLoader = Dog.class.getClassLoader();
Class clazz4 = classLoader.loadClass("cn.cyy.cl.common.bean.Dog");
System.out.println(clazz1.hashCode());
System.out.println(clazz2.hashCode());
System.out.println(clazz3.hashCode());
System.out.println(clazz4.hashCode());
}
}
#输出结果
685325104
685325104
685325104
685325104
获得常见数据类型的Class类对象 的后两种方法
四, 通过反射创建被反射类的实例对象 以及被反射类的 构造方法
- 通过反射创建类对象(注意这里指的是被反射的类的实例对象噢! )主要有两种方式:
通过Class对象的 newInstance();
通过Class对象获得的构造器的 new Instance();
4.1 clazz.newInstance();
Class clazz = Class.forName("cn.qsc.applefactory");
Apple apple = clazz.newInstance(); //从这里我们就拿到了Apple类的对象.
- 注意: 这种获取反射的类对象的方式, 在JDK1.9之后被废弃了, 取而代之的是第二种方法;
4.2 clazz.getConstructor().newInstance();
Class clazz = Class.forName("cn.qsc.appleFactory");
Apple apple = clazz.getConstructor().newInstance();
要特别注意两点:
- Class类对象获取构造器跟获取方法(Method), 字段(Fields)形式上是类似的, 使用
getDeclaredConstructor()
可以获取到public, private等等修饰的构造方法, 而getConstructor()
只能获取到使用public修饰的构造方法;
- 与上面第一种获取被反射类的实例对象的方法相比, 通过Constructor对象创建类对象可以选择特定参数列表的构造方法, 而第一种方法(使用Class对象.newInstance())则只能使用默认的无参构造方法.如下面🌰:
Class clazz = Apple.class; //等同于Class clazz = Class.forName("cn.qsc.applefactory")
//下面是使用(string, int)类型参数列表的构造方法创建了一个("红富士", 20)的apple实例对象.
Apple apple = (Apple)(clazz.getConstructor(String.class, int.class).new Instance("红富士", 20));
//要注意到, 在使用被反射类的实例对象时,
//要么对clazz加上泛型 Class<Apple> clazz = Apple.class;
//要么对得到的被反射类对象(实际上返回的是一个Object对象)进行强制类型转换
五, 利用反射得到被反射类的字段(属性)
- 通过 Class 对象的getField()方法可以获取被反射类的一条公有属性,但无法获取私有属性。
Class<Dog> clazzField = Dog.class; //获取Class类对象
Dog dog4 = clazzField.getConstructor().newInstance(); //获取被反射类的实例对象
Field publicField = clazzField.getField("field"); //得到被反射类的属性对象(class类对象.getFiled("字段名"))
System.out.println(publicField.get(dog4));// 被反射类的属性对象.get(被反射类的实例对象)
- 使用 Class对象的getDeclaredField()方法可以获取被反射类的一条私有或公有属性.
Field privateField = clazz.getDeclaredField("field1");
System.out.println(privateField.get(dog4));
- 使用Class对象getFields() /getDelcaredFileds() 可以获取到被反射类的公有的/所有公有私有的属性;
//获取字段数组( clazz.getFields())
Field[] fields = clazzField.getDeclaredFields();
for (Field field : fields) {
System.out.println("字段名称: "+ field.getName() + ",内容: "+field.get(dog4));
}
System.out.println("=====================================");
//通过反射, 修改字段的值 ( field对象.set(被反射类对象, 属性的新值))
privateField.set(dog4, "通过 getDeclaredField, 我们可以获取私有属性(字段)");
System.out.println(privateField.get(dog4));
System.out.println(privateField.getName()); //获取字段名, field.getName();
我们还可以通过field对象.set(被反射类对象, 属性的新值) 来修改被反射类中的属性(字段)的值;
六, 通过反射获取被反射类的方法
- 🌰:
///
//通过反射获取被反射类的各种方法并调用这些方法
// class对象-->被反射类的对象-->获取要调用的方法(传入参数类类型)-->调用方法(传入具体的参数值)
Class clazz5 = Class.forName("cn.cyy.cl.common.bean.Dog");
//获取被反射类的实例
Dog dog5 = (Dog)clazz5.newInstance();
//1. 获取公有的eat方法, 并调用, class类对象.getMethod("方法", 参数列表的数据类型的类对象);
Method eat = clazz5.getMethod("eat", String.class, int.class);
//调用方法, method.invoke(被反射类实例对象, 对应前面参数列表的具体参数值)
eat.invoke(dog5, "meat", 5);
//2. 获取私有的eat方法,
Method eatShit = clazz5.getDeclaredMethod("eatShit", String.class);
//获取被反射类的实例对象
//调用方法
eatShit.invoke(dog5, "xi lan hua");
Class Dog{
//整点dog类的方法
public void eat(String food, int kg){
System.out.println("dog eat food "+food + " "+kg+"kg");
}
//dog私有方法
private void eatShit(String food){
System.out.println("sometimes, dog eat some shit food :"+food);
}
}
七, 反射的优缺点
八, 补充: 通过反射获取被反射类的内部结构(构造方法, 字段, 方法等等)
-
参考资料:
- 老韩B站反射讲解(强推)
- https://zhuanlan.zhihu.com/p/86293659
- https://www.sczyh30.com/posts/Java/java-reflection-1/#8%E3%80%81%E5%88%A9%E7%94%A8%E5%8F%8D%E5%B0%84%E5%88%9B%E5%BB%BA%E6%95%B0%E7%BB%84
- https://www.zhihu.com/question/24304289
- 大白话说Java反射
- Java基础与提高干货系列——Java反射机制
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)