00-JAVA基础-反射机制

news2024/10/12 20:20:19

反射

什么是反射

在Java中,反射(Reflection)是Java语言的一个特性,它允许程序在运行时检查类、接口、字段和方法的信息。通过反射,Java代码能够动态地创建对象、调用方法、改变字段的值等,而无需在编译时知道这些类的详细信息。

Java反射主要提供了以下功能:

  1. 获取类的信息:通过反射,你可以获取一个类的Class对象,进而获取该类的名称、父类、实现的接口、声明的字段和方法等信息。
  2. 创建对象:使用反射,你可以在运行时动态地创建类的对象实例,而无需使用new关键字。
  3. 调用方法:通过反射,你可以获取类的方法信息,并动态地调用这些方法,包括私有方法。
  4. 访问和修改字段:反射允许你获取类的字段信息,并可以动态地读取和修改这些字段的值,包括私有字段。
  5. 注解处理:反射也常用于处理Java注解,读取注解信息并执行相应的逻辑。

反射的主要用途包括:

  1. 框架设计:许多Java框架(如Spring、Hibernate等)都大量使用了反射,以实现灵活的配置和扩展。
  2. 测试:在单元测试中,反射经常用于创建和操作测试对象。
  3. 插件机制:在需要支持插件的系统中,反射可以帮助系统在运行时加载和调用插件。

然而,反射也有一些缺点和注意事项:

  1. 性能开销:反射操作通常比直接操作慢,因为涉及了更多的动态解析和类型转换。
  2. 安全问题:通过反射,可以访问和修改类的私有成员,这可能导致安全问题。
  3. 代码可读性:过度使用反射可能导致代码难以理解和维护。

Class对象

Class对象是Java反射机制的核心,它封装了类的元数据(包含了关于类的结构信息,如类的名称、父类、实现的接口、字段、方法、注解等),并提供了在运行时检查和操作这些信息的能力。

每个类在JVM中都有且只有一个与之对应的Class对象,这个对象在类被加载到JVM时由类加载器创建。

案例
UserDo.java

package demo1;

/**
 * 定义UserDo用于测试反射
 *
 * @author Anna.
 * @date 2024/4/3 23:06
 */
public class UserDo {
    private String name;
    private Integer age;
    public String sex;

    public UserDo() {
    }

    public UserDo(String name, Integer age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    /**
     * 定义一个私有方法
     *
     * @param content
     * @return void
     * @author Anna.
     * @date 2024/4/3 23:09
     */
    private void sayHello(String content){System.out.printf("%s说%s%n",this.name,content);}

    @Override
    public String toString() {
        return "UserDo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

测试代码:

package demo1;

/**
 * 测试反射
 * 获取Class的三种方式
 *
 * @author Anna.
 * @date 2024/4/3 23:10
 */
public class ReflexDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 通过.class获取
        Class<UserDo> userDoClass1 = UserDo.class;
        System.out.println(userDoClass1);   // class demo1.UserDo

        // 通过UserDo实例调用getClass()方法获取
        Class<? extends UserDo> userDoClass2 = new UserDo().getClass();
        System.out.println(userDoClass2 == userDoClass1); // true

        // 通过Class.forName()获取
        Class<UserDo> userDoClass3 = (Class<UserDo>) Class.forName("demo1.UserDo");
        System.out.println(userDoClass3 == userDoClass2);   // true
    }
}

执行结果

在这里插入图片描述

获取Class对象

  1. 使用.class语法:Class<?> clazz = String.class;
  2. 使用对象的getClass()方法:String s = “Hello”; Class<?> clazz = s.getClass();
  3. 使用Class.forName(包路径.类名)方法(需处理ClassNotFoundException):Class<?> clazz = Class.forName(“java.lang.String”);

Class对象的作用

  1. 类实例化:使用newInstance()方法或反射API创建类的新实例(从Java 9开始,newInstance()方法已被弃用,应使用getDeclaredConstructor().newInstance())。
  2. 反射操作:通过Class对象可以获取类的字段、方法、注解等信息,并可以动态地调用方法、访问和修改字段。
  3. 类型检查与转换:Class对象可以用于运行时类型检查(instanceof)和类型转换(cast)。
  4. 注解处理:通过Class对象可以读取类、方法、字段上的注解信息

泛型与Class对象

泛型在Java中引入了类型参数的概念,但在运行时,由于类型擦除(Type Erasure),泛型类型信息并不完全保留。因此,使用反射处理泛型时可能会遇到一些限制。不过,可以通过一些技巧(如使用类型令牌)来在运行时保留部分泛型类型信息。(通过反射处理注解逻辑时详述)

安全性与性能

使用反射进行类操作时需要注意安全性和性能问题。反射可以绕过编译时的类型检查,从而可能导致运行时错误或安全漏洞。此外,反射操作通常比直接操作慢,因为它们涉及更多的动态解析和类型转换。

反射常用API

类型接口描述
java.lang.Class类-反射的核心类,提供了获取类的元数据信息的方法。
-getClass()调用类或接口实例的方法以获取其Class对象。
-.class语法直接通过类名获取其Class对象。
-Class.forName()通过类的全限定名获取其Class对象。
-getMethods()获取类的所有公共方法。
-getDeclaredMethods()获取类声明的所有方法,包括私有方法
-getConstructors()获取类的所有公共构造方法
-getDeclaredConstructors()获取类声明的所有构造方法,包括私有构造方法
-getFields()获取类的所有公共字段
-getDeclaredFields()获取类声明的所有字段,包括私有字段
-getAnnotations()获取类上的所有注解
-isAnnotationPresent()检查类上是否存在某个特定的注解。
java.lang.reflect.Method类-用于表示类的方法。
-invoke(Object obj, Object… args)调用方法,第一个参数是方法所属的对象,后面的参数是方法的参数。调用私有方法(需要先设置为可访问,通过设置method.setAccessible(true);实现)
-getParameterTypes()获取方法的参数类型数组。
-getReturnType()获取方法的返回类型。
java.lang.reflect.Field类-用于表示类的字段。访问私有字段需要设置为可访问(通过设置field.setAccessible(true);实现)
-get(Object obj)获取字段的值,第一个参数是字段所属的对象。
-set(Object obj)设置字段的值,第一个参数是字段所属的对象。
-getType()获取字段的类型。
java.lang.reflect.Constructor类-用于表示类的构造方法。
-newInstance(Object… initargs)使用此Constructor对象表示的构造方法来创建该构造方法的声明类的新实例。从Java 9开始,newInstance()方法已被弃用,应使用getDeclaredConstructor().newInstance()
java.lang.reflect.Modifier类-用于获取和操作修饰符的工具类。
-isPublic(int modifiers)检查修饰符是否表示类、方法或字段是公共的。
-isPrivate(int modifiers)判断修饰符是否是private。

注意:

setAccessible(true) 可以用来访问私有字段和方法,但这样会破坏封装性,并且可能带来安全风险。

反射操作通常比直接访问慢,因为它们涉及到更多的动态解析和类型转换。

使用反射时应谨慎处理异常,特别是ClassNotFoundException、NoSuchMethodException、IllegalAccessException和InvocationTargetException等。

反射API提供了非常强大的功能,但使用时也需要考虑到其潜在的安全和性能影响。在实际应用中,应仔细权衡反射带来的灵活性和可能带来的问题。

反射案例

package demo2;

import demo1.UserDo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * 测试反射
 * 1 通过反射创建UserDo对象,
 * 2 调用默认构造器创建对象
 * 3 调用公有方法设置name
 * 4 调用公有方法获取私有字段name属性值
 * 5 调用私有方法sayHello
 *
 * @author Anna.
 * @date 2024/4/3 23:19
 */
public class ReflexDemo {
    public static void main(String[] args) throws Exception {
        // 通过Class.forName()获取Class
        Class<?> clazz = Class.forName("demo1.UserDo");
        // 获取构造方法
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class, Integer.class, String.class);
        // 调用newInstance()初始化UserDo
        UserDo userDo = (UserDo) declaredConstructor.newInstance("张三", 20, "男");
        System.out.printf("输出初始化后的UserDo对象:%s%n", userDo);

        // 调用调用公有方法设置name
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
        // 方法调用前需要先初始化 否则会抛出 Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class
        // 从Java 9开始,newInstance()方法已被弃用,应使用getDeclaredConstructor().newInstance()
        UserDo userDo1 = (UserDo) clazz.getDeclaredConstructor().newInstance();
        // 执行共育setNameMethod方法
        setNameMethod.invoke(userDo1, "李四");

         调用公有方法获取私有字段name属性值
        Method getNameMethod = clazz.getMethod("getName");
        System.out.printf("通过反射调用getName:%s%n", getNameMethod.invoke(userDo1));

        // 因为已经拿到UserDo的实例也可以通过实例调用getName
        System.out.printf("通过实例调用getName:%s%n", userDo1.getName());

        // 通过反射调用私有方法sayHello
        Method sayHelloMethod = clazz.getDeclaredMethod("sayHello", String.class);
        // 调用私有方法需要设置为可访问 否则会抛出异常 Exception in thread "main" java.lang.IllegalAccessException: class demo2.ReflexDemo cannot access a member of class demo1.UserDo with modifiers "private
        sayHelloMethod.setAccessible(true);
        sayHelloMethod.invoke(userDo1, "通过反射调用私有方法sayHello说了你好啊");

    }
}

执行结果

在这里插入图片描述

比较反射与对象实例调用方法对比

package demo3;

import demo1.UserDo;

import java.lang.reflect.Method;

/**
 * 比较反射与对象实例调用方法对比
 *
 * @author Anna.
 * @date 2024/4/3 23:54
 */
public class ReflexDemo {
    public static void main(String[] args) throws Exception {
        test1();
        test2();
        test3();
    }

    /**
     * 通过UserDo调用方法
     *
     * @author Anna.
     * @date 2024/4/3 23:54
     */
    public static void test1(){
        UserDo userDo = new UserDo();
        long start = System.currentTimeMillis();
        for (long i = 0; i < 10000000000L; i++) {
            userDo.getName();
        }
        long end = System.currentTimeMillis();
        System.out.printf("通过UserDo调用getName()方法耗时(ms):%s%n", end - start);
    }

    /**
     * 通过反射调用getName方法,不设置跳过安全检查
     *
     * @author Anna.
     * @date 2024/4/3 23:57
     */
    public static void test2() throws Exception {
        // 获取Class
        Class<?> clazz = Class.forName("demo1.UserDo");
        // 初始化对象
        UserDo userDo = (UserDo) clazz.getDeclaredConstructor().newInstance();
        // 获取setName()方法
        Method getNameNameMethod = clazz.getMethod("getName");
        long start = System.currentTimeMillis();
        for (long i = 0; i < 10000000000L; i++) {
            getNameNameMethod.invoke(userDo);
        }
        long end = System.currentTimeMillis();
        System.out.printf("通过反射调用getName方法,不设置跳过安全检查(ms):%s%n", end - start);
    }

    /**
     * 通过反射调用getName方法,设置跳过安全检查
     *
     * @author Anna.
     * @date 2024/4/3 23:57
     */
    public static void test3() throws Exception {
        // 获取Class
        Class<?> clazz = Class.forName("demo1.UserDo");
        // 初始化对象
        UserDo userDo = (UserDo) clazz.getDeclaredConstructor().newInstance();
        // 获取setName()方法
        Method getNameNameMethod = clazz.getMethod("getName");
        // 设置跳过安全检查
        getNameNameMethod.setAccessible(true);
        long start = System.currentTimeMillis();
        for (long i = 0; i < 10000000000L; i++) {
            getNameNameMethod.invoke(userDo);
        }
        long end = System.currentTimeMillis();
        System.out.printf("通过反射调用getName方法,设置跳过安全检查(ms):%s%n", end - start);
    }
}

执行结果:

在这里插入图片描述

结论:
从执行结果可以看出,反射在性能方面是有所损耗的,但是设置setAccessible(true)可以适当提升反射效率。

反射操作泛型

泛型在Java中引入了类型参数的概念,但在运行时,由于类型擦除(Type Erasure),泛型类型信息并不完全保留。

为了通过单设操作这些类型以配合实际开发的需要,Java新增了ParameterizedType,GenericArrayType,TypeVariable,WildcardType几种类型来代表不能为归一到Class类中的类型但是又和原始类型起名的类型。

  • ParameterizedType :表示一种参数化的类型,比如Collection
  • GenericArrayType :表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable :是各种类型变量的公共接口
  • WildcardType:代表一种通配符类型表达式,比如:?,? extends Number, ?super Integer
package demo4;

import demo1.UserDo;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * 反射操作泛型
 *
 * @author Anna.
 * @date 2024/4/4 0:48
 */
public class ReflexDemo {
    public void test01(Map<String, UserDo> map, List<UserDo> userDoList) {
        System.out.println("调用test01");
    }

    public Map<String, UserDo> test02() {
        System.out.println("调用test02");
        return null;
    }

    public static void main(String[] args) throws Exception {
        // 获取制定法方法参数泛型信息
        Method test01Method = ReflexDemo.class.getMethod("test01", Map.class, List.class);
        // 获取方法的形式参数类型
        Type[] genericParameterTypes = test01Method.getGenericParameterTypes();
        for (Type genericParameter : genericParameterTypes) {
            // 判断形式参数类型是否为参数化的类型
            if (genericParameter instanceof ParameterizedType) {
                // 获取实际的类型参数
                Type[] actualTypeArguments = ((ParameterizedType) genericParameter).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.printf("参数,泛型类型:%s%n", actualTypeArgument);
                }
            }
        }

        // 获取制定法方法返回值泛型信息
        Method test02Method = ReflexDemo.class.getMethod("test02");
        // 获取方法的形式参数类型
        Type genericReturnType = test02Method.getGenericReturnType();
        // 判断返回值 类型是否为参数化的类型
        if (genericReturnType instanceof ParameterizedType) {
            // 获取实际的类型参数
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.printf("返回值,泛型类型:%s%n", actualTypeArgument);
            }
        }

    }
}

执行结果

在这里插入图片描述

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

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

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

相关文章

基础篇3 浅试Python爬虫爬取视频,m3u8标准的切片视频

浅试Python爬取视频 1.页面分析 使用虾米视频在线解析使用方式&#xff1a;https://jx.xmflv.cc/?url目标网站视频链接例如某艺的视频 原视频链接 解析结果: 1.1 F12查看页面结构 我们发现页面内容中什么都没有&#xff0c;video标签中的src路径也不是视频的数据。 1.2 …

linux清理缓存垃圾命令和方法介绍

在Linux系统中&#xff0c;清理缓存和垃圾文件可以通过多种方法完成&#xff0c;这些方法旨在释放磁盘空间、提高系统性能。以下是一些常用的方法&#xff0c;结合了搜索结果中的信息&#xff1a; 1. 使用sync和echo命令清除RAM缓存和交换空间1 清除页面缓存&#xff08;Page …

不讲概念,讲实操,mysql 分表模糊查询、分页查询 及 merge 表的使用

1.Mysql merge合并表的要求 1.合并的分表必须是 MyISAM 引擎&#xff0c;MyISAN引擎是不支持事务的。2.Merge表只保证合表后数据唯一性&#xff0c;合表前的数据可能会存在重复。3.表的结构必须一致&#xff0c;包括索引、字段类型、引擎和字符集。4.删除 tb_member1 分表正确…

原理图设计的通用规范

原理图各页内容依次为&#xff1a;封面、目录、电源、时钟、CPU、存储器、逻辑、背板&#xff08;母板&#xff09;接口等。 原理图上所有的文字方向应该统一&#xff0c;文字的上方应该朝向原理图的上方&#xff08;正放文字&#xff09;或左方&#xff08;侧放文字&#xff…

Whisper对于中文语音识别与转写中文文本优化的实践(Python3.10)

原文&#xff1a;Whisper对于中文语音识别与转写中文文本优化的实践(Python3.10) - 知乎 阿里的FunAsr对Whisper中文领域的转写能力造成了一定的挑战&#xff0c;但实际上&#xff0c;Whisper的使用者完全可以针对中文的语音做一些优化的措施&#xff0c;换句话说&#xff0c;…

【LeetCode热题100】51. N 皇后(回溯)

一.题目要求 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方…

hadoop:案例:将顾客在京东、淘宝、多点三家平台的消费金额汇总,然后先按京东消费额排序,再按淘宝消费额排序

一、原始消费数据buy.txt zhangsan 5676 2765 887 lisi 6754 3234 1232 wangwu 3214 6654 388 lisi 1123 4534 2121 zhangsan 982 3421 5566 zhangsan 1219 36 45二、实现思路&#xff1a;先通过一个MapReduce将顾客的消费金额进行汇总&#xff0c;再通过一个MapReduce来根据金…

RocketMQ是什么?

文章目录 一、RocketMQ是什么&#xff1f;二、RocketMQ 应用场景三、RocketMQ 优缺点1.优点2、缺点 一、RocketMQ是什么&#xff1f; RocketMQ 是一款纯 java、分布式、队列模型的消息中间件&#xff0c;支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。 二、Rocke…

java数据结构与算法刷题-----LeetCode417. 太平洋大西洋水流问题

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 深度优先遍历 深度优先遍历 解题思路&#xff1a;时间复杂度O( …

DIY蓝牙键盘(1) - 理解 键盘报文(免费)

DIY蓝牙键盘(1) - 理解键盘报文 1. 键盘报文体验 一个键盘对于用户的体验是&#xff0c;用户按按键A他能看到字母A会在主机上显示出来。那这是如何实现的&#xff1f; 其实很简单&#xff0c;只要键盘发送下面的两个报文给主机&#xff0c;字母A就能在主机上显示出来。 (1)…

【Qt】Ubuntu20.04.6+Qt5.15.2+QtCreator10.0.1无法输入中文

1、前提条件 1)已经安装了fcitx sudo apt install fcitx sudo apt install fcitx-pinyin sudo apt install fcitx-bin fcitx-table-all sudo apt install fcitx-qt52)系统已经配置fcitx 3)将系统下 /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitx…

CSS3新增的语法(三)【2D,3D,过渡,动画】

CSS3新增的语法&#xff08;三&#xff09;【2D,3D,过渡&#xff0c;动画】 10.2D变换10.1. 2D位移10.2. 2D缩放10.3. 2D旋转10.4. 2D扭曲&#xff08;了解&#xff09;10.5. 多重变换10.6. 变换原点 11. 3D变换11.1. 开启3D空间11.2. 设置景深11.3. 透视点位置11.4. 3D 位移11…

java数据结构与算法刷题-----LeetCode79. 单词搜索

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 回溯深度优先遍历 回溯深度优先遍历 解题思路&#xff1a;时间复…

Vue 大文件切片上传实现指南包会,含【并发上传切片,断点续传,服务器合并切片,计算文件MD5,上传进度显示,秒传】等功能

Vue 大文件切片上传实现指南 背景 在Web开发中&#xff0c;文件上传是一个常见的功能需求&#xff0c;尤其是当涉及到大文件上传时&#xff0c;为了提高上传的稳定性和效率&#xff0c;文件切片上传技术便显得尤为重要。通过将大文件切分成多个小块&#xff08;切片&#xff0…

Rust线程间通信通讯channel的理解和使用

Channel允许在Rust中创建一个消息传递渠道&#xff0c;它返回一个元组结构体&#xff0c;其中包含发送和接收端。发送端用于向通道发送数据&#xff0c;而接收端则用于从通道接收数据。不能使用可变变量的方式&#xff0c;线程外面修改了可变变量的值&#xff0c;线程里面是拿不…

UE5启用SteamOS流程

一、安装OnlineSubsystemSteam插件 1、在UE里安装OnlineSubsystemSteam 2、设置默认开始地图 3、设置DefaultEngine.ini文件&#xff1a; 打开项目根目录/Config/DefaultEngine.ini文件 打开官网的配置说明 复制并粘贴到该文件中 4、设置运行模式 5、测试 确保Steam平台已…

云原生:应用敏捷,华为视角下的应用现代化

Gartner 也提出&#xff0c;到 2023 年&#xff0c;新应用新服务的数量将达到 5 亿&#xff0c;也即是说&#xff1a;“每个企业都正在成为软件企业”。据IDC 预测&#xff0c;到 2025 年三分之二的企业将成为多产的“软件企业”&#xff0c;每天都会发布软件版本。越来越多的企…

【HTML】简单制作一个动态3D正方体

目录 前言 开始 HTML部分 JS部分 CSS部分 效果图 总结 前言 无需多言&#xff0c;本文将详细介绍一段代码&#xff0c;具体内容如下&#xff1a; 开始 首先新建文件夹&#xff0c;创建两个文本文档&#xff0c;其中HTML的文件名改为[index.html]&#xff0c;JS的文件名改…

基于Python深度学习的中文情感分析系统(V2.0)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

v3-admin-vite 改造自动路由,view页面自解释Meta

需求 v3-admin-vite是一款不错的后端管理模板&#xff0c;主要是pany一直都在维护&#xff0c;最近将后台管理也进行了升级&#xff0c;顺便完成一直没时间解决的小痛痒&#xff1a; 在不使用后端动态管理的情况下。我不希望单独维护一份路由定义&#xff0c;我希望页面是自解…