Java反射(原理剖析与使用)

news2024/11/24 4:12:00

一、反射机制是什么

1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
2、Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

二、反射的作用

我先讲两个案例来说明反射的重要性

大家可以通过案例过个眼熟,只需看懂有什么用即可,关于反射的知识,我会再后面一一讲解

案例1、通过配置文件的全类名,调用该类方法

在众多都框架中使用到了反射,能动态的生成一个对象,并操作该对象的属性与方法,最经典的就是各种注解和Spring框架的AOP等等

没有用到反射机制的时候,我们只能通过硬编码的方法更改代码中的逻辑,虽然这样的确是可行的

但是对于一个超大的项目来说,这样的维护非常耗成本,并且框架就得不到通过配置动态的更新,框架就会变得累赘,Java 根本也就流行不起来

(1)、配置一个properties文件,也可以用xml来配置

class=com.zhao.reflectdemo.pojo.Cat
name=小白

(2)、写两个实体类

Cat类
// @Data是LomBok的注解,如果不熟悉的小伙伴,可以认为他可以自动生成get、set方法等等
@Data
public class Cat {
    private String name;
    private Integer age;
    private Date brith;
    private String character;
    // 以下随便取的字段
    private String a;
    private String b;
    private String c;
    private String d;

    public void call(String name){
        this.name = name;
        System.out.println("猫儿:" + this.name + "在叫");
    }
}
Dog类
@Data
public class Dog {
    private String name;
    private Integer age;
    private Date brith;
    private String character;
    // 以下随便取的字段
    private String a;
    private String b;
    private String c;
    private String d;

    public void call(String name){
        this.name = name;
        System.out.println("狗儿:" + this.name + "在叫");
    }
}

(3)、测试:

    // 尝试用正常方法从配置文件中通过获取权类名,然后调用该类的call()方法
	// @Test为junit的注解,一般用于单元测试,因为一个类只有一个main方法,这个注解就是让该方法可以达到main方法的作用
    @Test
    public void test() throws Exception{
        // 从 properties 取出数据
        Properties properties = new Properties();
        properties.load(new InputStreamReader(
                Files.newInputStream(Paths.get("src\\main\\resources\\application.properties")),
                StandardCharsets.UTF_8)); // 不要在意我写的文件方法路径
        String className = properties.getProperty("class"); //目前值为:com.zhao.reflectdemo.pojo.Cat
        String name = properties.getProperty("name"); //目前值为:小白
        properties.clone(); // 关闭流
        // 正常方法中我们可以通过全类名,new一个对象,调用该类的方法
        Cat cat = new com.zhao.reflectdemo.pojo.Cat();
        cat.call(name); // 输出:猫儿:小白在叫

        // 那不通过反射我们可以,可以得到对象,并输出call()方法吗
//        Cat cat2 = new className(); // 当然是不可以的 className 指向的是一个字符串,并非一个类,使用这里肯定是会报错的
//        cat2.call(dogName);

        // 所以我们的主角 Class类 反射登场了
        common(className,name);

        // 更改 className 为 Dog类 全类名,当然可以在properties文件中更改,我这里就简化一下,免得本文冗长
        className = "com.zhao.reflectdemo.pojo.Dog";
        common(className,name);
    }
    // 通用方法,我们就可以知道,调用同样的方法,通过反射机制,就可以等到不同结果
    // 最大的不同就是,我们只通过两个字符串就实现了两个对象的方法
    private void common(String className,String name) throws Exception{
        Class<?> animalClass = Class.forName(className); // 通过全类名 得到Class 对象
        Object o = animalClass.newInstance(); // 生成该Cat类的对象
        Method call = animalClass.getMethod("call",String.class); // 得到 Method 对象,这个里面就是存储的该类的方法,因为我写的带参,所以这里也带String类的参数
        call.invoke(o,name);
    }

(4)、结果:

在这里插入图片描述

案例2、通过反射更改现有对象的属性

目前我们想用案例1中的Cat类,在初始化时,他的所有属性皆不是基本类型,所有默认初始值为null

我们想获取该存在的类并改变它的名字(name属性)为:大白,其他字符从null,改为""

如果不使用反射的话,我们就是一个个的通过set方法,改变该对象的属性,这样是可行的,但代码是不是会比较的冗长呢

进阶:如果说该类的熟悉为private,且没有任何方法能改变属性呢。

下面就一个通过案例解决上述问题

(1)、使用Cat类,去掉@Data注解和任何方法

public class Cat {
    private String name;
    private Integer age;
    private Date brith;
    private String character;
    // 以下随便取的字段
    private String a;
    private String b;
    private String c;
    private String d;
}

(2)、测试类:

    public void Test2() throws Exception {
        Cat cat = new Cat();
        common2(cat);
        System.out.println(cat);
    }
    private <T> void common2(T t) throws Exception{
        // 我这里直接塞的是对象了,对象也可以通过getClass方法,获取该对象的Class类
        Class<?> tClass = t.getClass(); // 通过泛型得到该类的Class对象
        Field[] fields = tClass.getDeclaredFields(); // 得到该类所有的属性(包括private)
        for (Field field : fields) {
            field.setAccessible(true); // 关闭验证,我们就可以操作私有属性(private)
            if (field.getType() == String.class) { // 如果获取到属性的类型是String
                String name = field.getName(); // 获取该 Class 类的属性名
                Object o = field.get(t); // 获取该类的该方法的值
                if (Objects.isNull(o)){ // 如果值是空的话
                    if (name.equals("name")) { // 如果获取到属性的名字是name
                        field.set(t,"大白"); // 设置name值为大白
                    }else {
                        field.set(t,""); // 设置属性值为 ""
                    }
                }
            }
        }
    }

(3)、结果:debug看一下,的确可以实现改熟悉的值

在这里插入图片描述

三、反射原理

要搞懂Java反射的原理,我们要从写好一个java文件,到编译成class文件,在通过类加载器加载到JVM的堆,加载类时由Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造,形成每个对象对应的全局唯一的一个Class方法

我就从Java程序计算机的三个阶段讲起:编译、类加载、运行

1、编译

一个我们编写的java文件,这个文件时面向与程序员的,可以通过javaSE很直观明了的代码的逻辑,但是计算机是看不懂的,计算机只能识别强弱电信号,就是以二进制形式存在的二进制文件,所以我们需要通过编译得到class字节码文件,再由jvm转换为计算机可识别的二进制数据

第一步,很简单就是通过javac命令,让java文件编译成class文件

图解

在这里插入图片描述

Java文件与Class文件对比

在这里插入图片描述

2、类加载

通过ClassLoader的defineClass 方法自动构造的,全局唯一的Class对象

把他的所有的成员变量、构造方法、成员方法封装为Class类中的Field数组、Constructor数组、Method数组,这个数组里面存放的就是类中的成员变量、构造方法、成员方法等信息

图解

在这里插入图片描述

DeBug看一下

在这里插入图片描述

查看Class对象是否是唯一的

在这里插入图片描述

3、运行

通过主方法运行 Cat cat = new Cat() 代码时,会先找对应的Class对象信息,把信息加载到堆中,生成一个cat对象

Class对象,可以想象成一个镜子,镜子里面有类的各种信息,反射就是指,通过这面镜子拿到自己想要的信息

在这里插入图片描述

四、使用反射

反射类集中在java.lang.reflect包下面

反射机制相关的重要的类

含义
java.lang.Class代表整个字节码。代表一个类型,代表整个类。
java.lang.reflect.Method代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

一、获取class对象的三种方法

如果使用的类形同,那么获取的是同一个Class对象

class对象一般要配合Method、Constructor、Field对象来使用

方法
Class.forName(“全类名”)
对象.getClass()
类.class
Cat cat = new Cat();
Class<? extends Cat> class1 = cat.getClass();
Class<Cat> class2 = Cat.class;
Class<?> class3 = Class.forName("com.zhao.reflectdemo.pojo.Cat");
System.out.println(class1 == class2); //true
System.out.println(class1 == class3); //true
System.out.println(class2 == class3); //true

二、Class类

其实这个类取的名字是带有误解性的,因为他是的的确确存在与java.lang包下的一个类,与我上述讲解的class对象,不是一个东西,但又有千丝万缕的关系

Class 类没有公共构造方法。Class 对象是在加载类时由Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

Class类常用的方法

特别强调一下newInstance()方法,可以反射实例化对象

方法名备注
public T newInstance()创建对象
public String getName()返回完整类名带包名
public String getSimpleName()返回类名
public Field[] getFields()返回类中public修饰的属性
public Field[] getDeclaredFields()返回类中所有的属性
public Field getDeclaredField(String name)根据属性名name获取指定的属性
public native int getModifiers()获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
ublic Method[] getDeclaredMethods()返回类中所有的实例方法
public Method getDeclaredMethod(String name, Class<?>… parameterTypes)根据方法名name和方法形参获取指定方法
public Constructor<?>[] getDeclaredConstructors()返回类中所有的构造方法
public Constructor getDeclaredConstructor(Class<?>… parameterTypes)根据方法形参获取指定的构造方法
public native Class<? super T> getSuperclass()返回调用类的父类
public Class<?>[] getInterfaces()返回调用类实现的接口集合

三、反射获取方法(Method)

Method类

方法名备注
public String getName()返回方法名
public int getModifiers()获取方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?> getReturnType()以Class类型,返回方法类型【一般配合Class类的getSimpleName()方法使用】
public Class<?>[] getParameterTypes()返回方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public Object invoke(Object obj, Object… args)调用方法

案例:

1、有个Cat类:

public class Cat {
    public void call(String name){
        System.out.println("cat的有参方法,参数:" + name);
    }
    public String call(){
        System.out.println("cat的无参方法");
        return "无参方法";
    }
    private String privatization(){
        System.out.println("cat的私有方法");
        return "私有方法";
    }
}

2、测试案例:

    @Test
    public void Test2() throws Exception {
        Class<Cat> catClass = Cat.class; // 获取Cat的class对象
        Cat cat = catClass.newInstance(); // 得到一个Cat对象,与 new Cat() 类似

        System.out.println("============获取单个非私有方法==================");
        Method method = catClass.getMethod("call"); // 获取单个非私有方法
        Object o = method.invoke(cat);
        System.out.println("method的返回值是" + o);
        Method method2 = catClass.getMethod("call",String.class); // 获取单个非私有方法,带参
        method2.invoke(cat, "随便");
//        Method method3 = catClass.getMethod("privatization"); // 获取单个私有方法 //会报错
//        method3.invoke(cat);

        // 由于数据里面的方法,包括Object类太多了,就不打印出来了,可以自己解开试一试
//        System.out.println("============获取多个非私有方法==================");
//        Method[] methods = catClass.getMethods(); // 获取多个非私有方法
//        for (Method method1 : methods) {
//            System.out.println("================for循环=============================");
//            // 获取修饰符列表
//            System.out.print("获取修饰符列表:");
//            System.out.println(method1.getModifiers());
//            // 获取方法的返回值类型
//            System.out.print("获取方法的返回值类型:");
//            System.out.println(method1.getReturnType().getSimpleName());
//            // 获取方法名
//            System.out.print("获取方法名:");
//            System.out.println(method1.getName());
//        }

        Method[] declaredMethods = catClass.getDeclaredMethods(); // 获取全部方法,包括私有
        System.out.println("============获取单个方法==================");
        Method declaredMethod = catClass.getDeclaredMethod("privatization"); //获取单个私有方法
        declaredMethod.setAccessible(true); // 关闭检查,这样才可以调用私有方法
        declaredMethod.invoke(cat);

    }

3、结果

在这里插入图片描述

四、反射获取字段(Field)

Field类

方法名备注
public String getName()返回属性名
public int getModifiers()获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?> getType()以Class类型,返回属性类型【一般配合Class类的getSimpleName()方法使用】
public void set(Object obj, Object value)设置属性值
public Object get(Object obj)读取属性值

案例:

1、有个Cat类:

@Data
public class Cat {
        //相信以大家的聪明才智肯定能举一反三,使用我就直接全部私有化属性了
    private String name;
    private Integer age;
    private Date brith;
    private String character;
    // 以下随便取的字段
    private String a;
    private String b;
    private String c;
    private String d;
}

2、测试案例:

借用一下案例二的例子

目前我们想用案例1中的Cat类,在初始化时,他的所有属性皆不是基本类型,所有默认初始值为null

我们想获取该存在的类并改变它的名字(name属性)为:大白,其他字符从null,改为""

    @Test
    public void Test3() throws Exception{
        //相信以大家的聪明才智肯定能举一反三,使用我就直接全部私有化属性了
        Cat cat = new Cat();
        cat.setA("随便,随便拉");
        Class<?> tClass = cat.getClass(); // 通过泛型得到该类的Class对象
        Field[] fields = tClass.getDeclaredFields(); // 得到该类所有的属性(包括private)
        for (Field field : fields) {
            field.setAccessible(true); // 关闭验证,我们就可以操作私有属性(private)
            if (field.getType() == String.class) { // 如果获取到属性的类型是String
                String name = field.getName(); // 获取该 Class 类的属性名
                Object o = field.get(cat); // 获取该类的该方法的值
                if (Objects.isNull(o)){ // 如果值是空的话
                    if (name.equals("name")) { // 如果获取到属性的名字是name
                        field.set(cat,"大白"); // 设置name值为大白
                    }else {
                        field.set(cat,""); // 设置属性值为 ""
                    }
                }
            }
        }
        System.out.println(cat);
    }

3、Debug

在这里插入图片描述

五、反射获取构造函数(Constructor)

Constructor类

方法名备注
public String getName()返回构造方法名
public int getModifiers()获取构造方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?>[] getParameterTypes()返回构造方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public T newInstance(Object … initargs)创建对象【参数为创建对象的数据】

案例:

1、有个Cat类:

public class Cat {
    public String name;

    private Cat(){

    }
    public Cat(String name){ this.name = name;}
}

2、测试案例:

    @Test
    public void Test4() throws Exception{
        Class<Cat> catClass = Cat.class;
        Constructor<Cat> constructor = catClass.getConstructor(String.class); // 获取非私有的构造函数
        Cat cat1 = constructor.newInstance("名字是个啥");
        System.out.println(cat1.name);
        Constructor<Cat> declaredConstructor = catClass.getDeclaredConstructor(); // 获取构造函数
        declaredConstructor.setAccessible(true); // 私有的要关闭检验
        Cat cat2 = declaredConstructor.newInstance(); // 通过私有的构造函数,得到Cat对象
        System.out.println(cat2.name);
    }

3、结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/479471.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年第二十届五一数学建模竞赛C题:“双碳”目标下低碳建筑研究-思路详解与代码答案

该题对于模型的考察难度较低&#xff0c;难度在于数据的搜集以及选取与处理。 这里推荐数据查询的网站&#xff1a;中国碳核算数据库&#xff08;CEADs&#xff09; https://www.ceads.net.cn/ 国家数据 国家数据​data.stats.gov.cn/easyquery.htm?cnC01 以及各省市《统…

第四届“长城杯”信息安全铁人三项赛决赛RE-obfuscating

这里主要是加了混淆 这里要用到IDA的一个插件D810和去混淆脚本deflat.py。值得注意的是deflat.py无法在主逻辑去混淆&#xff0c;这里可以参考这篇文章的脚本利用angr符号执行去除控制流平坦化 - 0x401RevTrain-Tools (bluesadi.github.io)。在使用deflat.py和这文章中的脚本轮…

【AI折腾录】stable web ui基础【sd安装、lora vae embedding hyperwork等基础概念】

目录 一 sd安装二 目标三 sd基础3.1 模型3.2 vae&#xff08;Variational autoencoder&#xff0c;变分自编码器&#xff09;3.3 embedding3.3.1 安装方式3.3.2 使用方式 3.4 Lora3.4.1 lora组成3.4.2 使用&#xff1a;3.4.3 效果3.4.4 测试不同CFG效果 3.5 hypernetworks 超网…

LeetCode_BFS_DFS_中等_1376.通知所有员工所需的时间

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 公司里有 n 名员工&#xff0c;每个员工的 ID 都是独一无二的&#xff0c;编号从 0 到 n - 1。公司的总负责人通过 headID 进行标识。 在 manager 数组中&#xff0c;每个员工都有一个直属负责人&#xff0c…

UE5实现距离测量功能

文章目录 1.实现目标2.实现过程2.1 Widget2.2 蓝图实现3.参考资料1.实现目标 UE5在Runtime环境下测量两个空间点位之间的绝对距离,并支持多段线的距离测量,GIF动图如下所示: 2.实现过程 实现原理比较简单,首先是基于PDI绘制线,有关绘制点和绘制线的可以看本专栏之前的文章…

css弹性布局

目录 1、实现弹性布局的前提&#xff1a;给父元素设置display:flex; 2、flex-direction&#xff1a;确定主轴方向 3、flex-wrap&#xff1a;是否换行 4、justify-content&#xff1a;主轴对齐方式 5、align-items&#xff1a;交叉轴对齐方式 6、align-content&#xff1a…

AWSFireLens轻松实现容器日志处理

applog应用程序和fluent-bit共享磁盘&#xff0c;日志内容是json格式数据&#xff0c;输出到S3也是JSON格式 applog应用部分在applog目录&#xff1a; Dockerfile文件内容 FROM alpine RUN mkdir -p /data/logs/ COPY testlog.sh /bin/ RUN chmod 777 /bin/testlog.sh ENTRYP…

人工智能技术在建筑能源管理中的应用场景

人工智能技术在建筑能源管理中的应用场景&#xff08;龙惟定&#xff09;&#xff0c;2021 摘 要 本文简要介绍了建筑能源管理(building energy management, BEM) 的概念。并从5个方面阐述了 BEM 对人工智能(AI) 技术的需求&#xff0c;即楼宇控制需要由从顶到底的基于物理模…

03-Vue技术栈之生命周期

目录 1、什么是生命周期2、分析生命周期2.1 生命周期钩子函数2.2 生命周期钩子函数的作用2.3 生命周期钩子函数图例2.4 生命周期钩子函数的应用 3、生命周期总结 1、什么是生命周期 又名&#xff1a;生命周期回调函数、生命周期函数、生命周期钩子。是什么&#xff1a;Vue在关…

ChatGPT实现编程语言转换

编程语言转换 对于程序员来说&#xff0c;往往有一类工作&#xff0c;是需要将一部分业务逻辑实现从服务端转移到客户端&#xff0c;或者从客户端转移到服务端。这类工作&#xff0c;通常需要将一种编程语言的代码转换成另一种编程语言的代码&#xff0c;这就需要承担这项工作…

Java多线程深入探讨

1. 线程与进程2. 创建和管理线程2.1. 继承Thread类2.2. 实现Runnable接口2.3 利用Callable、FutureTask接口实现。2.4 Thread的常用方法 3. 线程同步3.1. synchronized关键字3.1.1同步代码块&#xff1a;3.1.2 同步方法&#xff1a; 3.2. Lock接口 4. 线程间通信5. 线程池5.1 使…

vue - pc端实现对div的拖动功能

实现对div的拖动功能&#xff0c;需要先要知道以下的一些原生事件和方法&#xff1b; 1&#xff0c;事件: 方法描述onmousedown鼠标按钮被按下onmousemove鼠标被移动onmouseup鼠标按键被松开 2&#xff0c;方法: 方法描述event.clientX返回当事件被触发时鼠标指针相对于浏览…

【BIM+GIS】Supermap加载实景三维倾斜摄影模型

OSGB是常见的倾斜模型格式,本文讲述如何在Supermap中加载实景三维倾斜摄影模型OSGB。 文章目录 一、生成配置文件二、加载倾斜模型1. 新建场景2. 添加模型3. 高程调整一、生成配置文件 点击【三维数据】→【数据管理】→【生成配置文件】。 参数设置如下: 源路径:选择倾斜模…

【12.HTML入门知识-flexbox布局】

flexbox布局 1 认识flexbox2 flex布局的重要概念3 flex布局的模型4 flex container/items相关的属性4.1 flex-direction:主轴的方向决定布局方向4.2 flex-wrap 单行或者多行排列4.3 flex-flow 简写4.4 justify-content 主轴对齐方式4.5 align-item 交叉轴单行对齐方式4.6 align…

【数据结构】JDK HashMap源码解析

目录 &#x1f31f;HashMap源码解析 &#x1f308;类的属性 &#x1f308;构造方法 &#x1f308;put方法 &#x1f31f;对比常用Map的子类实现: &#x1f31f;HashMap源码解析 JDK8之前&#xff0c;HashMap就是数组链表&#xff1b; JDK8之后&#xff0c;变成了数组链表红…

基于C++/CLI实现C#与C++互调过程中的注意事项

目录 一、基于C/CLI 的调用原理二、注意事项如何基于VS2010完成C#调用C开发过程1、生成C应用程序&#xff08;非托管代码&#xff09;2、基于C/CLI生成托管代码3、C#调用 如何基于VS2010完成C调用C#开发过程 三、C/CLI与COM组件对比 一、基于C/CLI 的调用原理 C/CLI &#xff…

数据结构---队列的实现

文章目录 前言一、什么是队列&#xff1f;二、队列接口的实现 1.队列结构的定义2.接口实现总结 前言 队列是一种特殊的线性表。 特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&a…

【Vue.js】1668- 初中级前端必须掌握的 10 个 Vue 优化技巧

优化 Vue.js 应用性能是每个前端开发人员都需要关注的问题。本文我将分享 10 个初中级前端必须掌握的 Vue.js 优化技巧&#xff0c;无论您是正在学习 Vue.js&#xff0c;还是已经在应用开发中使用它&#xff0c;希望这些技巧都会对你的工作有所帮助。 1. 优雅的设置 v-for 中的…

Java 中常见的 Exception 有哪些

Java 是一种广泛使用的编程语言&#xff0c;它的强大和流行程度在很大程度上归功于它的异常处理机制。异常是在程序执行期间出现的错误或意外情况。在 Java 中&#xff0c;异常是通过抛出和捕获异常对象来处理的。在本文中&#xff0c;我们将介绍 Java 中的一些常见异常类型及其…

浅堆深堆解读

浅堆&#xff08;Shallow Heap&#xff09; 浅堆是指一个对象所消耗的内存。在32位系统中&#xff0c;一个对象引用会占据4个字节&#xff0c;一个int类型会占据4个字节&#xff0c;long型变量会占据8个字节&#xff0c;每个对象头需要占用8个字节。根据堆快照格式不同&#x…