

深入剖析Java中的反射,由浅入深,层层剥离! - JavaBuild
source link: https://www.cnblogs.com/JavaBuild/p/18002022
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

之前更新了不少Java的基础知识,比如Java的类、对象、基础类型、关键字、序列化、泛型、值传递等等,今天要上点深度了,来聊一聊Java中的 反射 !
所谓反射,就是在运行时分析、检查和操作类、接口、方法、属性的行为!
简单感受一下反射
在开始详解反射之前,我们先通过一段代码,简单的感受一下反射的魅力!首先,我们已经写了一个Person类,类中有age和name属性,并提供了set/get方法。这时候分别通过正常的实例化对象调用,和反射调用去操作对象的属性值!
【代码示例1】
//Java正射实现
Person person = new Person();
person.setName("JavaBuild-正射");
System.out.println(person.getName());
//Java反射实现
//获取类的class对象
Class<?> classa = Class.forName("com.javabuild.server.pojo.Person");
//通过class获取类的元数据-setName/getName方法
Method setName = classa.getMethod("setName", String.class);
Method getName = classa.getMethod("getName");
//获取默认空构造方法
Constructor constructor = classa.getConstructor();
//通过newInstance()进行实例化
Object o = constructor.newInstance();
//通过invoke方法进行方法调用
setName.invoke(o,"JavaBuild-反射");
System.out.println(getName.invoke(o));
以上为Java正射与反射的对比实现代码。从代码中我们可以看到,正常通过实例化对象后再调用相应方法的正射方式比通过反射的方式代码量要少很多,并且逻辑更加清晰明确。
反射被称之为框架的灵魂,在Java中被大量使用在框架、动态代理、注解等场景下;
- 开发框架:Spring、SpringBoot、Mybatis、Hibernate等框架中使用了反射
- 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
- 注解:注解本身只是起到一个标记符的作用,它需要利用反射机制,根据标记符去执行特定的行为。比如@Component注解就声明了一个类为 Spring Bean ,通过@Value注解读取配置文件中的值,这些的底层都是通过反射来实现需求。
我们基于上述代码示例1中的反射进行分步分析,彻底搞明白反射的实现与使用!
【步骤一】
通过如下这句代码获取反射类的Class对象
Class<?> classa = Class.forName("com.javabuild.server.pojo.Person");
可别小看这简简单单的一行代码,这里其实涉及到3个知识点,我们来扩展一下。
知识点一:什么是Class对象?
这里提到的Class和之间了解的用来标识类的class关键字并不一样!Class也是一个类,存放在java.lang包中,它的作用是:编译时生成一个类的Class对象,这个对象中包含了类的结构信息,如类名、继承父类、实现的接口、方法、属性等等,Class对象保存在编译后的.class文件中。
在我们new一个对象或者引用静态成员变量时,会将Class对象加载到JVM中,JVM解释器去分析Class对象获取到类的结构信息,创建我们需要的实例对象或者提供静态成员变量的引用值。
因为类的Class对象时存储在class文件中,所以不管实例化多少个对象,都有且仅有一个Class对象!因为Class类对象本身的特性,所以它可以在运行时操作类,这对反射来说无疑是天赐良缘!
知识点二:获取Class对象的几种方式?
1、在已知具体类的情况下
Class person= Person.class;
2、通过Class.forName()传入类的全路径获取,代码示例1中采用该方式
Class classa = Class.forName("com.javabuild.server.pojo.Person");
3、对象实例instance.getClass()获取
Person person = new Person();
Class aClass = person.getClass();
4、通过类加载器xxxClassLoader.loadClass()传入类路径获取
Class aClass = ClassLoader.getSystemClassLoader().loadClass("com.javabuild.server.pojo.Person");
知识点三:java.lang.reflect包中常用的反射类
在java.lang.reflect包中存着几个反射常用的类,大概的罗列如下,注意,Class类其实是放在java.lang中的。
Class:代表一个类或接口,包含了类的结构信息(如名称、构造函数、方法、字段等)。通过 Class 对象,可以获取类的元数据并操作类的实例。
Constructor:代表类的构造方法,用于创建类的实例。
Method:代表类的方法,可以通过它调用类的实例方法。
Field:代表类的字段,可以获取或修改字段的值。
Modifier:包含方法、字段和类的访问修饰符(如 public、private 等)。
【步骤二】
通过Class对象获取构造方法
Constructor constructor = classa.getConstructor();
除了这种获取默认无参构造的方式,获取构造方法还可以通过如下这些方式:
getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。
getConstructors():返回类的所有 public 构造方法。
getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。
【代码示例2】
Class<?> classa = Class.forName("com.javabuild.server.pojo.Person");
Constructor constructor01 = classa.getConstructor();
System.out.println(constructor);
System.out.println("---------------");
Constructor<?>[] constructors = classa.getConstructors();
for (Constructor<?> constructor02 : constructors) {
System.out.println(constructor02);
}
System.out.println("----------------");
Constructor declaredConstructor = classa.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);
【控制台输出】
public com.javabuild.server.pojo.Person()
---------------
public com.javabuild.server.pojo.Person()
public com.javabuild.server.pojo.Person(int)
public com.javabuild.server.pojo.Person(int,java.lang.String)
----------------
public com.javabuild.server.pojo.Person(int)
【步骤三】
通过 Constructor 对象初始化反射类对象
//通过newInstance()进行实例化
Object o = constructor.newInstance();
【步骤四】
获取要调用的方法的 Method 对象
//通过class获取类的元数据-setName/getName方法
Method setName = classa.getMethod("setName", String.class);
Method getName = classa.getMethod("getName");
【步骤五】
通过invoke方法进行方法调用
setName.invoke(o,"JavaBuild-反射");
以上即为反射使用的全过程!下面贴一下全量代码。
Person类
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//无参构造
public Person() {
}
//一个参数构造
public Person(int age) {
this.age = age;
}
//2个参数构造
public Person(int age, String name) {
this.age = age;
this.name = name;
}
}
反射测试类
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//Java正射实现
Person person = new Person();
person.setName("JavaBuild-正射");
System.out.println(person.getName());
//Java反射实现
//获取类的class对象
Class<?> classa = Class.forName("com.javabuild.server.pojo.Person");
//通过class获取类的元数据-setName/getName方法
Method setName = classa.getMethod("setName", String.class);
Method getName = classa.getMethod("getName");
//获取默认空构造方法
Constructor constructor = classa.getConstructor();
//通过newInstance()进行实例化
Object o = constructor.newInstance();
//通过invoke方法进行方法调用
setName.invoke(o,"JavaBuild-反射");
System.out.println(getName.invoke(o));
//获取默认空构造方法
Constructor constructor01 = classa.getConstructor();
System.out.println(constructor);
System.out.println("---------------");
Constructor<?>[] constructors = classa.getConstructors();
for (Constructor<?> constructor02 : constructors) {
System.out.println(constructor02);
}
System.out.println("----------------");
Constructor declaredConstructor = classa.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);
}
}
输出
JavaBuild-正射
JavaBuild-反射
public com.javabuild.server.pojo.Person()
---------------
public com.javabuild.server.pojo.Person()
public com.javabuild.server.pojo.Person(int)
public com.javabuild.server.pojo.Person(int,java.lang.String)
----------------
public com.javabuild.server.pojo.Person(int)
基于上面的内容,我们可以对java中的反射做一个总结吧
优点
1、可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。
缺点
1、破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题。
2、性能开销:由于反射涉及到动态解析,因此无法执行 Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。
Recommend
-
101
《看不见的客人》玩起了层层反转,可国产电影连讲故事都不会虎嗅注:本文转自微信公众号“影视圈”(ID:circlemag),原标题《<看不见的客人>:当我们在追逐大制作时,欧...
-
13
如何通过层层VPN加密代理找到你! WySec 2018-10-05 VPN网警...
-
5
dotnet 写一个支持层层继承属性的对象我最近在造一个比 Excel 差得多的表格控件,其中一个需求是属性的继承。大家都知道,表格里面有单元格,单元格里面允许放文本,文本可以放多段文本。本文的主角就是文本段落的样式属性,包括文本字体字号颜色等等属性...
-
5
苹果2022春季特别活动LOGO,多色层层推叠,开启高能传送! - 标志情报局3月3日,
-
6
聚焦315之网贷好难(一)| 点击贷款即被层层推荐 野蛮导流何时休
-
6
翡翠直播卖假层层设套、自导自演,平台也需担起责任 第一财经 4小时后 听新闻 作者:王海 ▪ 陆涵之 责编:刘佳 上一...
-
9
作者 | vivo 互联网服务器团队- Wei Ling本文主要讲述的是如何根据公司网络架构和业务特点,锁定正常请求被误判为跨域的原因并解决。一、问题描述某一个业务后台在表单提交的时候,报跨域错误,具体如下图:
-
9
在《深入剖析Java中的反射,由浅入深,层层剥离!》这篇文章中我们讲反射时,曾提到过Java的动态代理中使用了反射技术,那么好,今天我们要就着反射的索引,来学习一下Java中的代理!
-
2
Promise, async, await实现异步编程,代码详解
-
6
在Java的世界里万物皆对象。但我认为是万物皆数据,世界由各种各样数据构建起来,我们通过程序去实现数据的增删改查、转入转出、加减乘除等等,不同语言的实现方式殊途同归。由此可见,数据对于程序语言的重要性。 这段话是在写数据类型那篇博客时说的,当时...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK