0
点赞
收藏
分享

微信扫一扫

【再学一次系列】new对象不行吗?为什么要用反射?

黄昏孤酒 2022-01-26 阅读 46

前言

记得在第一次在学校接触反射的时候就对自己三连问:“这玩意重要吗?这玩意有啥用?为啥不直接new对象?”。直到后来出来工作,接触了一些三方框架,再加上自己也参与一些公司基础框架开发,才意识到反射不可谓不是Java框架开发的神兵利器。

简介

简单来说,在程序运行时,反射允许我们可以以编程的方式动态操作任意一个已经加载的类/对象的所有的属性和方法。(概念懵逼没关系,铁子们请继续向下看)

JVM装载类的过程

在反射正文开始前,我们要先聊一聊JVM装载类的过程,看如下代码:

/**
 * person类
 * @author : uu
 * @version : v1.0
 * @Date 2022/1/24
 */
@Data
public class Person {
    private String name;
}

// 测试类
@Test
public void test(){
    System.out.println("new 之前");
	Person person = new Person();
	System.out.println("new 之后");
}
复制代码

启动测试时请在VM配置参数:-XX:+TraceClassLoading打印类的加载日志,启动后控制台打印如下数据:

...
new 之前
[Loaded com.uucoding.advance.entity.Person from file:/Users/uu/IdeaProjects/uu-study/uu-java-study/thinking-java/advance-java-example/target/classes/]
new 之后
...
复制代码

该行打印的内容表示从编译的class文件中加载Person类。我们来画个简单的图理解理解上面这些代码的流程:

  1. 首先程序启动,Person.java文件编译成Person.class文件;
  2. 程序执行到new Person(),开辟内存空间
  3. 类加载器将Person.class加载到JVM内存中;
  4. 将Person.class加载到JVM的同时也会将person对象,以及Person类的Class对象加载到JVM内存中。

不知道大家看完上述流程会不会出现如下问题:

  • 没有new对象,启动会不会加载该类?

  • new了对象,不调用,启动会不会加载该类?

  • 类真的是只会被加载一次吗?

问题实操答疑

上面的三个问题,我也不知道,但是我们可以有效的利用我们手头的idea来测试一下。

问题一:没有new对象,启动会不会加载该类?

  • 验证步骤

    1. 去掉测试方法中的 new Person()
    2. 启动测试类,VM参数需要配置为-XX:+TraceClassLoading
    3. 查看类的加载日志是否包含Person类
  • 验证代码如下:

@Test
public void testEmpty(){
    // VM 配置 -XX:+TraceClassLoading
    // 不new对象,查看Person类是否被加载
}
复制代码
  • 验证结果

    • 日志中无Person类,没有new对象,启动不会加载该类

问题二:new了对象,不调用,启动会不会加载该类?

  • 验证步骤

    1. 引入spring-boot-starter-web依赖
    2. 构建SpringBoot启动入口
    3. 构建一个测试接口,方法体增加 new Person()
    4. 启动SpringBoot程序,VM参数需要配置为-XX:+TraceClassLoading
    5. 查看类的加载日志是否包含Person类
  • 验证代码如下:

// 引入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<version>2.6.2</version>
</dependency>
复制代码
/**
 * 启动类
 * @author : uu
 * @version : v1.0
 * @Date 2022/1/24
 */
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
/**
 * 验证反射controller
 * @author : uu
 * @version : v1.0
 * @Date 2022/1/24
 */
@RestController
@RequestMapping("/test")
public class ReflectController {
    /**
     * 验证请求才会加载class
     * @return
     * @throws Exception
     */
    @GetMapping("/lazyNew")
    public void lazyNew() throws Exception {
        new Person();
    }
}
复制代码
  • 验证结果

    • 没有Person类,如果new了对象,不调用,启动不会加载该类
  • 继续验证(如果请求该接口,类会不会被加载?)

    1. 浏览器/Postman请求该接口
    2. 查看类的加载日志是否包含Person类
  • 继续验证结果

    • 请求后控制台打印出加载Person类
  • 结论

    • 通过验证其实可以看的出来,只有被系统使用的类才会被调用。

问题三:类的真的只会被加载一次吗?

基于问题二的代码,我们重复调用相同的接口,Person类不会被重复加载!

结论

只有被系统使用的类才会被JVM加载,且每个类只会被加载一次,类加载后会生成一个Class对象,该Class对象中包含这个类的所有信息!

Class

通过类加载的流程我们知道,类被加载后会生成一个对前类的Class对象,该Class对象包含了这个类的所有信息,而反射就是通过读取这个Class对象,反向获取类的相关信息,并对其进行相关操作。

Class对象的获取方式

  • 通过类获取
public void testCreateClass() {
    // 通过类获取
    Class<Person> personClass = Person.class;
    System.out.println(personClass.getName()); // com.uucoding.advance.entity.Person
}
复制代码
  • 通过对象获取
public void testCreateClass() {
    // 通过对象获取
    Person person = new Person();
    Class<? extends Person> personClass2 = person.getClass();
    System.out.println(personClass2.getName()); // com.uucoding.advance.entity.Person
}
复制代码
  • 通过类路径获取
public void testCreateClass() throws ClassNotFoundException {
    // 通过类路径获取
    Class<?> personClass3 = Class.forName("com.uucoding.advance.entity.Person");
    System.out.println(personClass3.getName()); // com.uucoding.advance.entity.Person
}
复制代码

注:关于Class的具体使用本章暂不讨论,将放在下一章进行全面讲解!

为什么要用反射?

回归主题,上面啰哩啰嗦半天,看起来好像反射也没啥特殊的呀!不如我们举个🌰,工厂模式大家应该都比较了解(或看这里工厂案例新玩法,用Lambda重构设计模式),起初我们是通过传入一个key,获取一个对象。那么如果新增或者减少类,都要对工厂进行调整,但是如果利用反射,根本就不需要考虑工厂的变化,只需要我们有哪些类,现在要用什么即可!

反射缺点

以下描述来自官方文档,详见:👉反射文档 见【Drawbacks of Reflection】部分

反射很强大,但不应该乱用,因为使用反射会给我们带来一些困扰,比如:

  • 性能开销:反射涉及动态解析类型,因此无法执行某些Java虚拟机的优化,应该避免在性能敏感功能中使用反射
  • 安全限制:反射需要在安全管理器下运行时可能不存在的运行时权限。对于必须在受限安全上下文中运行的代码(例如在 Applet 中),这是一个重要的考虑因素。
  • 内部暴露:反射允许代码执行在非反射代码中非法的操作,例如访问private字段和方法,因此使用反射可能会导致意想不到的副作用,这可能会导致代码功能失调并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

源码

  • 👉源码请点击这里访问

总结

  • 本章主要阐述反射的一些前置知识,主要包括类的加载机制、反射的优缺点;
  • Class类是反射的基础,任何类都是Class的实例对象;
  • JVM在加载的时候会把类的类名、所在包、构造函数、属性、方法打包对应的Class对象中;
  • 反射能够获取类的属性方法等,本质上就是操作这个类Class对象。
举报

相关推荐

0 条评论