Java 反射详解:动态创建实例、调用方法和访问字段

news2024/11/20 6:36:24

“一般情况下,我们在使用某个类之前已经确定它到底是个什么类了,拿到手就直接可以使用 new 关键字来调用构造方法进行初始化,之后使用这个类的对象来进行操作。”

Writer writer = new Writer();
writer.setName("少年");

像上面这个例子,就可以理解为“正射”。而反射就意味着一开始我们不知道要初始化的类到底是什么,也就没法直接使用 new 关键字创建对象了。

我们只知道这个类的一些基本信息,就好像我们看电影的时候,为了抓住一个犯罪嫌疑人,警察就会问一些目击证人,根据这些证人提供的信息,找专家把犯罪嫌疑人的样貌给画出来——这个过程,就可以称之为反射。

Class clazz = Class.forName("com.itwanger.s39.Writer");
Method method = clazz.getMethod("setName", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object,"少年");

像上面这个例子,就可以理解为“反射”。“反射的缺点主要有两个。”

  • 破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题。
  • 性能开销:由于反射涉及到动态解析,因此无法执行 Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。

反射的主要应用场景有:

  • 开发通用框架:像 Spring,为了保持通用性,通过配置文件来加载不同的对象,调用不同的方法。
  • 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
  • 注解:注解本身只是起到一个标记符的作用,它需要利用发射机制,根据标记符去执行特定的行为。

举例:

public class Writer {
    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 class ReflectionDemo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Writer writer = new Writer();
        writer.setName("少年");
        System.out.println(writer.getName());

        Class clazz = Class.forName("com.itwanger.s39.Writer");
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();

        Method setNameMethod = clazz.getMethod("setName", String.class);
        setNameMethod.invoke(object, "少年");
        Method getNameMethod = clazz.getMethod("getName");
        System.out.println(getNameMethod.invoke(object));
    }
}

结果:

少年
少年

只不过,反射的过程略显曲折了一些。

第一步,获取反射类的 Class 对象:

 Class clazz = Class.forName("com.itwanger.s39.Writer");

在 Java 中,Class 对象是一种特殊的对象,它代表了程序中的类和接口。

Java 中的每个类型(包括类、接口、数组以及基础类型)在 JVM 中都有一个唯一的 Class 对象与之对应。这个 Class 对象被创建的时机是在 JVM 加载类时,由 JVM 自动完成。

Class 对象中包含了与类相关的很多信息,如类的名称、类的父类、类实现的接口、类的构造方法、类的方法、类的字段等等。这些信息通常被称为元数据(metadata)。

除了前面提到的,通过类的全名获取 Class 对象,还有以下两种方式:

  • 如果你有一个类的实例,你可以通过调用该实例的getClass()方法获取 Class 对象。例如:String str = “Hello World”; Class cls = str.getClass();
  • 如果你有一个类的字面量(即类本身),你可以直接获取 Class 对象。例如:Class cls = String.class;
    第二步,通过 Class 对象获取构造方法 Constructor 对象:
Constructor constructor = clazz.getConstructor();

第三步,通过 Constructor 对象初始化反射类对象:

Object object = constructor.newInstance();

第四步,获取要调用的方法的 Method 对象:

Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");

第五步,通过 invoke() 方法执行:

setNameMethod.invoke(object, "少年");
getNameMethod.invoke(object)

要想使用反射,首先需要获得反射类的 Class 对象,每一个类,不管它最终生成了多少个对象,这些对象只会对应一个 Class 对象,这个 Class 对象是由 Java 虚拟机生成的,由它来获悉整个类的结构信息。

也就是说,java.lang.Class 是所有反射 API 的入口。

而方法的反射调用,最终是由 Method 对象的 invoke() 方法完成的,来看一下源码(JDK 8 环境下)。

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
        InvocationTargetException {
    // 如果方法不允许被覆盖,进行权限检查
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            // 检查调用者是否具有访问权限
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    // 获取方法访问器(从 volatile 变量中读取)
    MethodAccessor ma = methodAccessor;
    if (ma == null) {
        // 如果访问器为空,尝试获取方法访问器
        ma = acquireMethodAccessor();
    }
    // 使用方法访问器调用方法,并返回结果
    return ma.invoke(obj, args);
}

两个嵌套的 if 语句是用来进行权限检查的。

invoke() 方法实际上是委派给 MethodAccessor 接口来完成的。

在这里插入图片描述
MethodAccessor 接口有三个实现类,其中的 MethodAccessorImpl 是一个抽象类,另外两个具体的实现类继承了这个抽象类。
在这里插入图片描述

  • NativeMethodAccessorImpl:通过本地方法来实现反射调用;
  • DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;
    通过 debug 的方式进入 invoke() 方法后,可以看到第一次反射调用会生成一个委派实现 DelegatingMethodAccessorImpl,它在生成的时候会传递一个本地实现 NativeMethodAccessorImpl。
    在这里插入图片描述

也就是说,invoke() 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法。

也就是说,invoke() 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法。

“之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 Java 到 C/C++ 再到 Java 之间的切换过程,而动态实现不需要;但如果反射调用的次数比较少,反而本地实现更快一些。
那临界点是多少呢?“默认是 15 次。“可以通过 -Dsun.reflect.inflationThreshold 参数类调整。”

来看下面这个例子。

Method setAgeMethod = clazz.getMethod("setAge", int.class);
for (int i = 0;i < 20; i++) {
    setAgeMethod.invoke(object, 18);
}

在 invoke() 方法处加断点进入 debug 模式,当 i = 15 的时候,也就是第 16 次执行的时候,会进入到 if 条件分支中,改变 DelegatingMethodAccessorImpl 的委派模式 delegate 为 (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(),而之前的委派模式 delegate 为 NativeMethodAccessorImpl。
在这里插入图片描述
“接下来,我们再来熟悉一下反射当中常用的 API。”

1)获取反射类的 Class 对象
Class.forName(),参数为反射类的完全限定名

Class c1 = Class.forName("com.itwanger.s39.ReflectionDemo3");
System.out.println(c1.getCanonicalName());

Class c2 = Class.forName("[D");
System.out.println(c2.getCanonicalName());

Class c3 = Class.forName("[[Ljava.lang.String;");
System.out.println(c3.getCanonicalName());

输出结果:

com.itwanger.s39.ReflectionDemo3
double[]
java.lang.String[][]

类名 + .class,只适合在编译前就知道操作的 Class。

Class c1 = ReflectionDemo3.class;
System.out.println(c1.getCanonicalName());

Class c2 = String.class;
System.out.println(c2.getCanonicalName());

Class c3 = int[][][].class;
System.out.println(c3.getCanonicalName());

来看一下输出结果:

com.itwanger.s39.ReflectionDemo3
java.lang.String
int[][][]

2)创建反射类的对象

  • 用 Class 对象的 newInstance() 方法。
  • 用 Constructor 对象的 newInstance() 方法
Class c1 = Writer.class;
Writer writer = (Writer) c1.newInstance();

Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();
Object object = constructor.newInstance();

3)获取构造方法

Class 对象提供了以下方法来获取构造方法 Constructor 对象:

  • getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
  • getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。
  • getConstructors():返回类的所有 public 构造方法。
  • getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。
Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();

Constructor[] constructors1 = String.class.getDeclaredConstructors();
for (Constructor c : constructors1) {
    System.out.println(c);
}

4)获取字段

大体上和获取构造方法类似,把关键字 Constructor 换成 Field 即可。

Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");

5)获取方法

大体上和获取构造方法类似,把关键字 Constructor 换成 Method 即可。

Method[] methods1 = System.class.getDeclaredMethods();
Method[] methods2 = System.class.getMethods();

注意,如果想反射访问私有字段和(构造)方法的话,需要使用 Constructor/Field/Method.setAccessible(true) 来绕开 Java 语言的访问限制。

参考:https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

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

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

相关文章

2024年软考-官方最新考试安排出来了,软考新调整,很重要,但也很惹人气愤

官方最新通知&#xff0c;关于2024年度计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试工作计划 笔试改机考后&#xff0c;必然会迎来调整&#xff0c;但有点让人费解。 这次调整变动主要是每年考试的次数调整&#xff0c;很多改为了一年一考&#xff0c;具体…

Centos8 yum方式安装Redis

Centos8 yum方式安装多个Redis 是否安装GCC依赖 ggc -v #或者 rpm -q gcc安装GCC yum install -y gcc如果不是管理员 加 sudo sudo yum install -y gcc yum安装Redis yum install redis失败更新yum 再安装 #添加EPEL仓库 sudo yum install epel-release#更新yum源 sudo yum upd…

Newsmy储能电源与您相约九州汽车生态博览

2024年3月7日—10日&#xff0c;第24届 深圳国际智慧出行、汽车改装及汽车服务业态博览会&#xff08;以下简称“九州汽车生态博览会”&#xff09;将在深圳国际会展中心&#xff08;宝安&#xff09;举办&#xff0c;Newsmy纽曼集团将在3号馆32523展位&#xff0c;携全系产品与…

拼多多3.9元的手机支架,在视频号卖15.9元

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 万万没想到&#xff1a;拼多多3.9元的手机支架&#xff0c;在视频号竟然要卖15.9元。 更让人想不到的是&#xff1a;视频号商家竟然是从拼多多发货的&#xff0c;足足赚了4倍差价。 更更更让人想不到的是&#xff1a…

2024年第一届CS2major,新胶囊即将发行,需要提前做哪些布局

2024年第一届CS2major&#xff0c;将会在3月17日哥本哈根开始。 所以&#xff1a; 1、新的胶囊大概率会在3月10日左右发布。 2、网传战队挂坠&#xff0c;不知道是否会出现&#xff1f;&#xff08;原本出现过战队布章包&#xff0c;由于销量太差&#xff0c;第二届就取消了…

《花书》学习:LeNet

# LeNet网络架构 正常的应该是&#xff1a;输入→操作→输出 但都简化 要么省略 操作 要么省略 输出 # LeNet第一个卷积层详解

实现session共享的方法总结完整版

文章目录 实现session共享的方法总结完整版1、使用共享数据库&#xff1a;2、使用粘性会话&#xff08;Sticky Session&#xff09;&#xff1a;3、使用缓存系统&#xff1a;4、使用分布式文件系统&#xff1a;5、使用中央认证服务&#xff1a;6、使用会话复制&#xff1a;7、使…

【软件测试】一个扫码支付的二维码怎么测(测试点分析)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 面试的时候&#…

linux小记(1)

基本概念&#xff1a;不依靠扩展名来区分文件类型 好处&#xff1a;除了文本文件其他所有windows文件都无法在Linux下运行&#xff0c;包括病毒木马。 坏处&#xff1a;所有的软件都需要对linux单独开发 习惯用后缀来区分文件&#xff0c;方便管理。 -压缩包&#xff1a;*.…

nginx生成自签名SSL证书

备注&#xff1a;nginx自生成的ssl证书在浏览器访问时会提示此证书不受信用 1.安装nginx nginx必须有"--with-http_ssl_module"模块 查看nginx安装的模块&#xff1a; [rootmaster1 key]# nginx -V nginx version: nginx/1.24.0 built by gcc 4.8.5 20150623 (Red …

我的第②个出海工具站 - 2024年50个出海工具站计划

为了大家更好的使用各种出海工具。我上线了一版 出海工具导航 站点&#xff0c;经常使用的可以收藏下&#xff0c;我文内使用的网站都集成在了这里&#xff0c;非常使用。 随着AIGC的到来&#xff0c;2024年到了海外工具回暖的一年。今年计划上线50款出海工具站计划&#xff0c…

[SS]语义分割_U-Net

U-Net网络结构讲解视频 从零开始的U-net入门 U-Net详解 研习U-Net改进 目录 一、介绍 二、详解 1、网络结构 2、网络运行过程 3、实验现状 4、分割策略 一、介绍 U-Net是一种用于生物医学图像分割的卷积神经网络架构。它由Olaf Ronneberger等人在2015年提出&#x…

YOLOv8改进 在更换的PoolFormer主干网络中增加注意力机制

一、PoolFormer的网络结构 PoolFormer采用自注意力机制和池化操作相结合的方式&#xff0c;同时考虑了局部和全局的特征关系。 具体的代码如&#xff08;YOLOv8改进 更换多层池化操作主干网络PoolFormer_yolov8池化-CSDN博客&#xff09;所示。 二、Global Attention Mechan…

Redis面试总结

概述 1. Redis是什么&#xff1f;简述它的优缺点&#xff1f; Redis本质上是一个Key-Value类型的内存数据库&#xff0c;很像Memcached&#xff0c;整个数据库加载在内存当中操作&#xff0c;定期通过异步操作把数据库中的数据flush到硬盘上进行保存。 因为是纯内存操作&…

JavaScript基础3之面向对象关于面向过程、函数式编程、对比、构造函数、原型

JavaScript基础 面向对象面向过程函数式编程命令式编程函数式编程特性副作用透明引用不可变变量函数是一等公民 常见的函数式编程模型 面向对象为什么要使用面向对象封装继承多态 对比面向过程函数式编程面向对象 构造函数原型constructor使用场景 对象原型 面向对象 面向过程…

5年爬到半山腰,我后悔了吗?

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 软件测试是一个付出就有回报的工作&#xff0c;可能很多人会说软…

Starknet(strk) 跨链桥教程:手把手教你用bitget钱包跨链

摘要&#xff1a;通过Rhino.fi &#xff0c;将资产无缝桥接至Starknet&#xff08;web3.bitget.com/zh/assets/starknet-wallet&#xff09;变得高效且具有成本效益&#xff0c;Rhino.fi 是一个以其快速处理时间和低交易费用而闻名的平台。它专为与 MetaMask 等流行的 Web 3 钱…

mybatis的xml文件如何配置能被识别

为了让MyBatis能够识别和使用XML Mapper文件&#xff0c;你需要确保这些文件被正确放置和配置。下面是确保MyBatis XML Mapper文件被识别的步骤&#xff1a; 1. 正确放置XML Mapper文件 通常&#xff0c;XML Mapper文件应该放在src/main/resources目录下。为了更好的组织这些…

Vanna-ai -基于RAG的TextToSql实现方案

官方连接&#xff1a;Vanna.AI - Personalized AI SQL Agent 1.背景 基于大模型的TextToSql的关键为给大模型提供正确有效的数据库信息及问题&#xff0c;以提升大模型生成sql的正确率。database_info question形成prompt&#xff0c;但是实际中通常会遇到一个问题&#xff…

Python+更改镜像源下载库+PyCharm+汉化+第一个项目配置

文章目录 一、Python二、更改镜像源下载库三、PyCharm四、汉化五、第一个项目配置 2024年3月5日 操作环境&#xff1a; Win11-23H2 Python-3.12.2 PyCharm-2023.3.4 一、Python https://www.python.org/ 点击Download&#xff0c;查看对应的版本&#xff08; prerelease…