百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 博客教程 > 正文

说实话,你真的了解反射吗?

connygpt 2024-12-17 13:26 4 浏览

1. 啥是反射

1.初识反射

刚开始学反射的时候,我是一脸懵逼的,这玩意真的是“抽象的妈妈给抽象开门-抽象到家了。”

为什么创建对象要先获取 Class 对象?这不多此一举吗?我直接 new 一下不是更简单吗?

什么是程序运行时获取类的属性和方法?平时都是程序编译出错了再修改代码,我为什么要考虑程序运行时的状态?

我平时开发也用不到,学这玩意有啥用?

后来学了注解、spring、SpringMVC 等技术之后,发现反射无处不在。

2.JVM 加载类

我们写的 java 程序要放到 JVM 中运行,所以要学习反射,首先需要了解 JVM 加载类的过程。

1.我们写的 .java 文件叫做源代码。

2.我们在一个类中写了一个 main 方法,然后点击 IDEA 的 run 按钮,JVM 运行时会触发 jdk 的 javac 指令将源代码编译成 .class 文件,这个文件又叫做字节码文件。

3.JVM 的类加载器(你可以理解成一个工具)通过一个类的全限定名来获取该类的二进制字节流,然后将该 class 文件加载到 JVM 的方法区中。

4.类加载器加载一个 .class 文件到方法区的同时会在堆中生成一个唯一的 Class 对象,这个 Class 包含这个类的成员变量、构造方法以及成员方法。

5.这个 Class 对象会创建与该类对应的对象实例。

所以表面上你 new 了一个对象,实际上当 JVM 运行程序的时候,真正帮你创建对象的是该类的 Class 对象。

也就是说反射其实就是 JVM 在运行程序的时候将你创建的所有类都封装成唯一一个 Class 对象。这个 Class 对象包含属性、构造方法和成员方法。你拿到了 Class 对象,也就能获取这三个东西。

你拿到了反射之后(Class)的属性,就能获取对象的属性名、属性类别、属性值,也能给属性设置值。

你拿到了反射之后(Class)的构造方法,就能创建对象。

你拿到了反射之后(Class)的成员方法,就能执行该方法。

3.反射的概念

JAVA 反射机制是在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。

知道了 JVM 加载类的过程,相信你应该更加深入的了解了反射的概念。

反射:JVM 运行程序 --> .java 文件 --> .class 文件 --> Class 对象 --> 创建对象实例并操作该实例的属性和方法

接下来我就讲一下反射中的相关类以及常用方法。

2. Class 对象

获取 Class 对象

先建一个 User 类:

public class User {
    private  String name = "知否君";
    public String sex = "男";

    public User() {
    }

    public User(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public void eat(){
        System.out.println("人要吃饭!");
    }

    private void run(){
        System.out.println("人要跑步!");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}
复制代码

获取 Class 对象的三种方式:

1. Class.forName("全类名")

全类名:包名+类名

Class userClass = Class.forName("com.xxl.model.User");
复制代码

2. 类名.class

Class userClass = User.class;
复制代码

3. 对象.getClass()

User user = new User();
Class userClass = user.getClass();
复制代码

尽管有三种方式获取 Class 对象,但是我们一般采用上述第一种方式。

拿到 Class 对象之后,我们就可以操作与它相关的方法了。

3. 获取类名

1.获取完整类名:包名+类名

getName()

Class userClass = Class.forName("com.xxl.model.User");
String name = userClass.getName();
System.out.println(name);
复制代码

打印结果:

2.获取简单类名:不包括包名

getSimpleName()

Class userClass = Class.forName("com.xxl.model.User");
String simpleName = userClass.getSimpleName();
System.out.println(simpleName);
复制代码

打印结果:

4. 属性

4.1 获取属性

1.获取所有公有属性:public 修饰

getFields()

Class userClass = Class.forName("com.xxl.model.User");
Field[] fields = userClass.getFields();
for (Field field : fields) {
    System.out.println(field);
}
复制代码

打印结果:

2.获取单个公有属性

getField("属性名")

Class userClass = Class.forName("com.xxl.model.User");
Field field = userClass.getField("sex");
System.out.println(field);
复制代码

打印结果:

3.获取所有属性:公有+私有

getDeclaredFields()

Class userClass = Class.forName("com.xxl.model.User");
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field);
}
复制代码

打印结果:

4.获取单个属性:公有或者私有

getDeclaredField("属性名")

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
Field sexField = userClass.getDeclaredField("sex");
System.out.println(nameField);
System.out.println(sexField);
复制代码

打印结果:

4.2 操作属性

1.获取属性名称

getName()

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
System.out.println(nameField.getName());
复制代码

打印结果:

2.获取属性类型

getType()

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
System.out.println(nameField.getType());
复制代码

打印结果:

3.获取属性值

get(object)

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("sex");
User user = new User();
System.out.println(nameField.get(user));
复制代码

打印结果:

注: 通过反射不能直接获取私有属性的值,但是可以通过修改访问入口来获取私有属性的值。

设置允许访问私有属性:

field.setAccessible(true);
复制代码

例如:

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
User user = new User();
System.out.println(nameField.get(user));
复制代码

打印方法:

4.设置属性值

set(object,"属性值")

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
User user = new User();
nameField.set(user,"张无忌");
System.out.println(nameField.get(user));
复制代码

打印结果:

5. 构造方法

1.获取所有公有构造方法

getConstructors()

Class userClass = Class.forName("com.xxl.model.User");
Constructor[] constructors = userClass.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println(constructor);
}
复制代码

打印结果:

2.获取与参数类型匹配的构造方法

getConstructor(参数类型)

Class userClass = Class.forName("com.xxl.model.User");
Constructor constructor = userClass.getConstructor(String.class, String.class);
System.out.println(constructor);
复制代码

打印结果:

6. 成员方法

6.1获取成员方法

1.获取所有公共方法

getMethods()

Class userClass = Class.forName("com.xxl.model.User");
Method[] methods = userClass.getMethods();
for (Method method : methods) {
    System.out.println(method);
}
复制代码

打印结果:

我们发现,打印结果除了自定义的公共方法,还有继承自 Object 类的公共方法。

2.获取某个公共方法

getMethod("方法名", 参数类型)

Class userClass = Class.forName("com.xxl.model.User");
Method method = userClass.getMethod("setName", String.class);
System.out.println(method);
复制代码

打印结果:

3.获取所有方法:公有+私有

getDeclaredMethods()

Class userClass = Class.forName("com.xxl.model.User");
Method[] declaredMethods = userClass.getDeclaredMethods();
for (Method method : declaredMethods) {
    System.out.println(method);
}
复制代码

打印结果:

4.获取某个方法:公有或者私有

getDeclaredMethod("方法名", 参数类型)

Class userClass = Class.forName("com.xxl.model.User");
Method method = userClass.getDeclaredMethod("run");
System.out.println(method);
复制代码

打印结果:

6.2 执行成员方法

invoke(object,"方法参数")

Class userClass = Class.forName("com.xxl.model.User");
Method method = userClass.getDeclaredMethod("eat");
User user = new User();
method.invoke(user);
复制代码

打印结果:

注: 通过反射不能直接执行私有成员方法,但是可以设置允许访问。

设置允许执行私有方法:

method.setAccessible(true);
复制代码

7. 注解

1.判断类上或者方法上时候包含某个注解

isAnnotationPresent(注解名.class)
复制代码

例如:

Class userClass = Class.forName("com.xxl.model.User");
if(userClass.isAnnotationPresent(Component.class)){
    Component annotation = (Component)userClass.getAnnotation(Component.class);
    String value = annotation.value();
    System.out.println(value);
};
复制代码

2.获取注解

getAnnotation(注解名.class)
复制代码

例如:

Class userClass = Class.forName("com.xxl.model.User");
// 获取类上的注解
Annotation annotation1 = userClass.getAnnotation(Component.class);
Method method = userClass.getMethod("eat");
// 获取方法上的某个注解
Annotation annotation2 = userClass.getAnnotation(Component.class);
复制代码

8. 创建类的实例

1.通过 Class 实例化对象

Class.newInstance()

Class userClass = Class.forName("com.xxl.model.User");
User user = (User)userClass.newInstance();
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());
复制代码

打印结果:

2.通过构造方法实例化对象

constructor.newInstance(参数值)

Class userClass = Class.forName("com.xxl.model.User");
Constructor constructor = userClass.getConstructor(String.class, String.class);
User user = (User)constructor.newInstance\("李诗情", "女"\);
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());
复制代码

打印结果:

9. 反射案例

有一天技术总监对张三说:"张三,听说你最近学反射了呀。那你设计一个对象的工厂类给我看看。"

张三心想:"哟,快过年了,领导这是要给我涨工资啊。这次我一定好好表现一次。"

5分钟过后,张三提交了代码:

public class ObjectFactory {
    
    public static User getUser() {
        
        User user = null;
        try {
            Class userClass = Class.forName("com.xxl.model.User");
            user = (User) userClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return user;
    }

    public static UserService getUserService() {
        UserService userService = null;
        try {
            Class userClass = Class.forName("com.xxl.service.impl.UserServiceImpl");
            userService = (UserService) userClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return userService;
    }
}

复制代码

技术总监瞄了一眼代码,对张三说:"你这个工厂类存在两个问题。"

1.代码存在大量冗余。如果有一万个类,你是不是要写一万个静态方法?

2.代码耦合度太高。如果这些类存放的包路径发生改变,你再用 forName()获取 Class 对象是不是就会有问题?你还要一个个手动改代码,然后再编译、打包、部署。。你不觉得麻烦吗?

“发散你的思维想一下,能不能只设计一个静态类,通过传参的方式用反射创建对象,传递的参数要降低和工厂类的耦合度。顺便提醒你一下,可以参考一下 JDBC 获取数据库连接参数的方式。”

张三一听:"不愧是总监啊,醍醐灌顶啊!等我 10 分钟。"

10 分钟后,张三再次提交了代码:

object.properties

user=com.xxl.model.User
userService=com.xxl.service.impl.UserServiceImpl
复制代码

ObjectFactory

public class ObjectFactory {

    private static Properties objectProperty = new Properties();

    // 静态方法在类初始化时执行,且只执行一次
    static{
        try {
            InputStream inputStream = ObjectFactory.class.getResourceAsStream("/object.properties");
            objectProperty.load(inputStream);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static Object getObject(String key){
        Object object = null;
        try {
            Class objectClass = Class.forName(objectProperty.getProperty(key));
            object = objectClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
}
复制代码

测试方法:

@Test
  void testObject() {
      User user = (User)ObjectFactory.getObject("user");
      UserService userService = (UserService)ObjectFactory.getObject("userService");
      System.out.println(user);
      System.out.println(userService);
  }
复制代码

执行结果:

总监看后连连点头,笑着对张三说:“用 properties 文件存放类的全限定名降低了代码的耦合度,通过传参的方式使用反射创建对象又降低了代码的冗余性,这次改的可以。"

"好啦,今晚项目要上线,先吃饭去吧,一会还要改 bug。”

张三:"..........好的总监。"

10. 反射的作用

我们或多或少都听说过设计框架的时候会用到反射,例如 Spring 的 IOC 就用到了工厂模式和反射来创建对象,BeanUtils 的底层也是使用反射来拷贝属性。所以反射无处不在。

尽管我们日常开发几乎用不到反射,但是我们必须要搞懂反射的原理,因为它能帮我们理解框架设计的原理。

原文链接:https://juejin.cn/post/7055090668619694094

相关推荐

3分钟让你的项目支持AI问答模块,完全开源!

hello,大家好,我是徐小夕。之前和大家分享了很多可视化,零代码和前端工程化的最佳实践,今天继续分享一下最近开源的Next-Admin的最新更新。最近对这个项目做了一些优化,并集成了大家比较关注...

干货|程序员的副业挂,12个平台分享

1、D2adminD2Admin是一个完全开源免费的企业中后台产品前端集成方案,使用最新的前端技术栈,小于60kb的本地首屏js加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助...

Github标星超200K,这10个可视化面板你知道几个

在Github上有很多开源免费的后台控制面板可以选择,但是哪些才是最好、最受欢迎的可视化控制面板呢?今天就和大家推荐Github上10个好看又流行的可视化面板:1.AdminLTEAdminLTE是...

开箱即用的炫酷中后台前端开源框架第二篇

#头条创作挑战赛#1、SoybeanAdmin(1)介绍:SoybeanAdmin是一个基于Vue3、Vite3、TypeScript、NaiveUI、Pinia和UnoCSS的清新优...

搭建React+AntDeign的开发环境和框架

搭建React+AntDeign的开发环境和框架随着前端技术的不断发展,React和AntDesign已经成为越来越多Web应用程序的首选开发框架。React是一个用于构建用户界面的JavaScrip...

基于.NET 5实现的开源通用权限管理平台

??大家好,我是为广大程序员兄弟操碎了心的小编,每天推荐一个小工具/源码,装满你的收藏夹,每天分享一个小技巧,让你轻松节省开发效率,实现不加班不熬夜不掉头发,是我的目标!??今天小编推荐一款基于.NE...

StreamPark - 大数据流计算引擎

使用Docker完成StreamPark的部署??1.基于h2和docker-compose进行StreamPark部署wgethttps://raw.githubusercontent.com/a...

教你使用UmiJS框架开发React

1、什么是Umi.js?umi,中文可发音为乌米,是一个可插拔的企业级react应用框架。你可以将它简单地理解为一个专注性能的类next.js前端框架,并通过约定、自动生成和解析代码等方式来辅助...

简单在线流程图工具在用例设计中的运用

敏捷模式下,测试团队的用例逐渐简化以适应快速的发版节奏,大家很早就开始运用思维导图工具比如xmind来编写测试方法、测试点。如今不少已经不少利用开源的思维导图组件(如百度脑图...)来构建测试测试...

【开源分享】神奇的大数据实时平台框架,让Flink&Spark开发更简单

这是一个神奇的框架,让Flink|Spark开发更简单,一站式大数据实时平台!他就是StreamX!什么是StreamX大数据技术如今发展的如火如荼,已经呈现百花齐放欣欣向荣的景象,实时处理流域...

聊聊规则引擎的调研及实现全过程

摘要本期主要以规则引擎业务实现为例,陈述在陌生业务前如何进行业务深入、调研、技术选型、设计及实现全过程分析,如果你对规则引擎不感冒、也可以从中了解一些抽象实现过程。诉求从硬件采集到的数据提供的形式多种...

【开源推荐】Diboot 2.0.5 发布,自动化开发助理

一、前言Diboot2.0.5版本已于近日发布,在此次发布中,我们新增了file-starter组件,完善了iam-starter组件,对core核心进行了相关优化,让devtools也支持对IAM...

微软推出Copilot Actions,使用人工智能自动执行重复性任务

IT之家11月19日消息,微软在今天举办的Ignite大会上宣布了一系列新功能,旨在进一步提升Microsoft365Copilot的智能化水平。其中最引人注目的是Copilot...

Electron 使用Selenium和WebDriver

本节我们来学习如何在Electron下使用Selenium和WebDriver。SeleniumSelenium是ThoughtWorks提供的一个强大的基于浏览器的开源自动化测试工具...

Quick 'n Easy Web Builder 11.1.0设计和构建功能齐全的网页的工具

一个实用而有效的应用程序,能够让您轻松构建、创建和设计个人的HTML网站。Quick'nEasyWebBuilder是一款全面且轻巧的软件,为用户提供了一种简单的方式来创建、编辑...