【Java技术专题】「攻破技术盲区」带你攻破你很可能存在的Java技术盲点之动态性技术原理指南(反射技术专题)

news2024/9/20 14:50:41

带你攻破你很可能存在的Java技术盲点之动态性技术原理指南

  • 带你攻破你很可能存在的Java技术盲点之动态性技术原理指南
  • 编程语言的类型
    • 静态类型语言
    • 动态类型语言
  • 技术核心方向
    • 反射API
    • 反射案例介绍
      • 反射功能操作
        • 获取构造器
          • 长度可变的参数 - 构造方法
            • 使用反射 API 获取参数长度可变的构造方法
        • 获取Field域
          • 使用反射 API 获取和使用静态域和实例域
        • 获取Method方法
          • 获取和调用对象中的公开和私有方法的示例
        • 操作数组
          • 使用反射 API 操作数组
        • 访问权限与异常处理
  • 内容总结

带你攻破你很可能存在的Java技术盲点之动态性技术原理指南

本系列技术专题的相关技术指南主要有以下三个方面:
在这里插入图片描述

编程语言的类型

学习一门新的动态类型语言可能需要花费较长的时间,使得已经熟悉Java的开发人员更希望继续使用Java来解决问题。然而,Java本身也支持动态性,在一些需要灵活性的场合可以发挥作用。反射API就是Java中的一个例子,它能够在运行时通过方法名称查找并调用方法。Java语言也在不断更新版本,提高对动态性和灵活性的支持。

整体的编程语言分为三大类:静态类型语言和动态类型语言、半静态半动态类型语言。
在这里插入图片描述

静态类型语言

Java语言是一种静态类型的编程语言,即要在编译时进行类型检查。在Java中,每个变量的类型需要在声明时显式指定;所有变量、方法的参数和返回值的类型必须在程序运行之前就已经确定。这种静态类型特性使得编译器能够在编译时进行大量的类型检查,从而发现代码中明显的类型错误。然而,这也意味着代码中包含了大量不必要的类型声明,使代码显得过于冗长且不够灵活。相对应的,动态类型语言(如JavaScript和Ruby等)的类型检查则是在运行时进行的。在这类语言中,源代码中的变量类型可以在运行时动态确定。

动态类型语言

相比于静态类型语言,动态类型语言(如JavaScript和Ruby等)的类型检查是在运行时进行的。在这类语言中,源代码中不需要显式地声明类型,因此,使用动态类型语言编写的代码更加简洁。近年来,动态类型语言的流行也反映了语言中动态性的重要性。适当的动态性对于提高开发效率非常有帮助,因为它可以减少开发人员需要编写的代码量。

技术核心方向

虽然Java是一种静态类型语言,但是它也提供了使代码更具灵活性的动态性特性。这些特性包括脚本语言支持API、反射API、动态代理和JSR292中引入的动态语言支持。开发人员可以选择不同的方式来提高代码的灵活性。例如,可以使用脚本语言支持API将脚本语言集成到Java程序中,使用反射API在运行时动态调用方法,使用动态代理拦截接口方法调用,或使用JSR292中的方法句柄来实现更多的功能。方法句柄支持多种变换操作,并能满足不同场合的需求。在这里插入图片描述

反射API

反射API是Java语言提供的动态性支持,它允许程序在运行时获取Java类的内部结构,如构造方法、域和方法等,并与它们进行交互。反射API也能实现许多动态语言常用的实用功能。按照面向对象的思路,应该通过方法来改变对象的状态,而不是直接修改属性的值。Java类中的属性设置和获取方法名通常遵循JavaBeans规范,以setXxx和getXxx命名。因此,可以编写一个工具类,用于设置和获取任何符合JavaBeans规范的对象的属性。

可以使用Java的反射API实现与JavaScript语言的实现类似的功能,代码量上并不太有差别。实现思路是先从对象的类中查找方法,再调用该方法并传入参数。这个静态方法可以被作为一个实用工具方法在程序中使用。

public class ReflectSetter
   public static void invokeSetter(Object obj,String field,Object value) throws NoSuchMethodException,InvocationTargetException,IllegalAccessException{
     String methodName "set"+field.substring(0,1).toUppercase() + field.substring(1);
     class<?>clazz obj.getclass();
     Method method clazz.getMethod (methodName,value.getclass ())
     method.invoke (obj,value);
 }
}

从上述示例可以看出,反射API可以实现Java语言的灵活使用。实际上,反射API定义了提供者和使用者之间的松散契约,这种契约可以在方法调用时只需要建立在名称和参数类型上,而不需要在代码中首先声明变量。这种方式提供了更大的灵活性和动态性,但也需要开发者自己保证调用的合法性。如果方法调用不合法,相关的异常会在运行时抛出。

反射案例介绍

反射API常用于方法名或属性名按照特定规则变化的情况:

  • 在Servlet中,利用反射API可以遍历HTTP请求中的所有参数,然后用invokeSetter方法填充领域对象的属性值。
  • 在数据库操作中,也通过反射API实现从查询结果集中创建并填充领域对象的场景。这些对应关系都可以通过反射API来建立。

反射功能操作

反射API虽然能为Java程序带来灵活性,但其实现机制也会带来性能代价。通过反射调用方法一般比直接在源代码中编写的方式慢一到两个数量级。虽然随着Java虚拟机的改进,反射API的性能得到了提升,但在一些对性能要求高的应用中,需要慎用反射API。
在这里插入图片描述

获取构造器

可以通过反射API获取Java类中的构造方法,从而在运行时动态地创建Java对象。具体步骤如下:

  1. 获取Class类的对象,可以使用Class.forName方法或者类的.class属性。

  2. 通过Class类的getConstructors方法获取所有的公开构造方法的列表,或者使用getConstructor方法根据参数类型获取公开的构造方法。如果需要获取类中真正声明的构造方法,可以使用getDeclaredConstructors和getDeclaredConstructor方法。

  3. 得到表示构造方法的java.lang.reflect.Constructor对象之后,可以通过其getName方法获取构造方法的名称,getParameterTypes方法获取构造方法的参数类型,getModifiers方法获取构造方法的修饰符等信息。

  4. 最后,可以使用newInstance方法创建出新的对象,该方法接受一个可变参数列表,用于传递构造方法的参数值。如果构造方法没有参数,则可以直接调用newInstance方法。

需要注意的是,使用反射API创建对象的效率较低,应该尽量避免在性能要求较高的场景中使用。

一般的构造方法的获取和使用并没有什么特殊之处,需要特别说明的是对参数长度可变的构造方法和嵌套类(nested class)的构造方法的使用。

长度可变的参数 - 构造方法

如果一个构造方法声明了长度可变的参数,需要使用对应的数组类型的 Class 对象来获取该构造方法,因为长度可变的参数实际上是通过数组来实现的。

使用反射 API 获取参数长度可变的构造方法

例如,如果一个类 VarargsConstructor 的构造方法包含 String 类型的可变长度参数,调用getDeclaredConstructor 方法时需要使用 String[].class,否则会找不到该构造方法。在调用newInstance 方法时,需要将作为实际参数的字符串数组先转换为 Object 类型,以避免方法调用时的歧义,这样编译器就知道将该字符串数组作为一个可变长度的参数来传递。


public class VarargsConstructor {
	public VarargsConstructor(String... names) {}
}

public void useVarargsConstructor() throws Exception { 
	Constructor<VarargsConstructor> constructor = VarargsConstructor.class.
		getDeclaredConstructor(String[].class);
	constructor.newInstance((Object) new String[]{"A", "B", "C"});
}

获取嵌套类的构造方法时,需要区分静态和非静态两种情况。
在这里插入图片描述静态嵌套类,可以按照一般的方式来使用。

非静态嵌套类,其特殊之处在于它的对象实例中都有一个隐含的对象引用,指向包含它的外部类对象。这个隐含的对象引用的存在,使得非静态嵌套类中的代码可以直接引用外部类中包含的私有域和方法。因此,在获取非静态嵌套类的构造方法时,类型参数列表的第一个值必须是外部类的 Class 对象。

例如,对于非静态嵌套类 NestedClass,获取其构造方法时需要传入外部类的 Class 对象作为第一个参数,以便在创建新对象时传递外部对象的引用。

static class StaticNestedClass {
	public StaticNestedClass(String name) {}
}
class NestedClass {
	public NestedClass(int count) {}
}
public void useNestedClassConstructor() throws Exception {
	Constructor< StaticNestedClass> sncc = StaticNestedClass.class. getDeclaredConstructor(String.class);
	sncc.newInstance("Alex");
	Constructor<NestedClass> ncc = NestedClass.class.getDeclaredConstructor(ConstructorUsage.class, int.class);
	NestedClass ic = ncc.newInstance(this, 3);
}

获取Field域

通过反射 API,可以获取类中的域(field),包括公开的静态域和对象中的实例域。获取表示域的 java.lang.reflect.Field 类的对象之后,就可以获取和设置域的值。与获取构造方法的方法类似,Class 类中也有 4 个方法用来获取域,分别是 getFields、getField、getDeclaredFields 和 getDeclaredField。
在这里插入图片描述

  • getFields 方法返回公开的静态域和对象中的实例域;
  • getField 方法返回指定名称的公开的静态域或对象中的实例域;
  • getDeclaredFields 方法返回类中所有的域,包括私有的静态域和对象中的实例域;
  • getDeclaredField 方法返回指定名称的域,包括私有的静态域和对象中的实例域。
使用反射 API 获取和使用静态域和实例域

获取和使用静态域和实例域的示例,两者的区别在于使用静态域时不需要提供具体的对象实例,使用 null 即可

Field 类中除了操作 Object 的 get 和 set 方法之外,还有操作基本类型的对应方法,包括 getBoolean / setBoolean、getByte / setByte、getChar / setChar、getDouble / setDouble、getFloat / setFloat、getInt / setInt 和 getLong / setLong 等

public void useField() throws Exception {
	Field fieldCount = FieldContainer.class.getDeclaredField("count");
	fieldCount.set(null, 3);
	Field fieldName = FieldContainer.class.getDeclaredField("name"); 
	FieldContainer fieldContainer = new FieldContainer(); 
	fieldName.set(fieldContainer, "Bob");
}

总的来说,获取和设置类中的公开域比较简单,但是无法通过反射 API 获取或操作私有域。

获取Method方法

最常使用反射 API 的场景是获取对象中的方法,并在运行时调用该方法。Class 类中有 4 个方法用来获取方法,分别是 getMethods、getMethod、getDeclaredMethods 和 getDeclaredMethod。这些方法的作用类似于获取构造方法和域的对应方法。通过获取表示方法的 java.lang.reflect.Method 类的对象,可以查询该方法的详细信息,例如方法的参数和返回值的类型等。使用 invoke 方法可以传入实际参数并调用该方法。

获取和调用对象中的公开和私有方法的示例
public void useMethod() throws Exception { 		
	MethodContainer mc = new MethodContainer();
	Method publicMethod = MethodContainer.class.getDeclaredMethod("publicMethod");
	publicMethod.invoke(mc);
	Method privateMethod = MethodContainer.class.getDeclaredMethod("privateMethod");
	privateMethod.setAccessible(true);
	privateMethod.invoke(mc);
}

需要注意的是,在调用私有方法之前,需要先调用 Method 类的setAccessible方法来设置可以访问的权限。与构造方法和域不同的是,通过反射 API 可以获取到类中的私有方法。

操作数组

利用反射API对数组进行操作的方式有所不同于一般的Java对象。需要使用java.lang.reflect.Array这个实用工具类来实现。该类提供了创建数组和操作数组元素的方法。newInstance方法用来创建新的数组。第一个参数是数组中元素的类型,后面的参数是数组的维度信息。

String[] names = ( Array.newInstance(int.class, 3, 3, 3);
double[][][] arrays= (double[][][]) Array.newInstance(double[][].class, 2, 2);
使用反射 API 操作数组

例如,可以使用下面的示例代码创建一个长度为10的一维String数组和一个3x3x3的三维数组:

public void useArray() {
	String[] names = (String[]) Array.newInstance(String.class, 10);
	names[0] = "Hello"; 
	Array.set(names, 1, "World");
	String str = (String) Array.get(names, 0);
	int[][][] matrix1 = (int[][][]) Array.newInstance(int.class, 3, 3, 3);
	matrix1[0][0][0] = 1;
	int[][][] matrix2 = (int[][][]) Array.newInstance(int[].class, 3, 4);
	matrix2[0][0] = new int[10]; 
	matrix2[0][1] = new int[3]; 
	matrix2[0][0][1] = 1;
}

需要注意的是,尽管在创建时只声明了两个维度,但是matrix2实际上也是一个三维数组,因为它的元素类型是double。

访问权限与异常处理

使用反射 API 可以绕过 Java 语言中默认的访问控制权限,例如访问在另一个类中声明的私有方法。这是通过调用继承自 java.lang.reflect.AccessibleObject 的 setAccessible 方法来实现的。在使用 invoke 方法调用方法时,如果方法本身抛出异常,invoke 方法会抛出 InvocationTargetException 异常来表示这种情况。可以通过 InvocationTargetException 异常的 getCause 方法获取真正的异常信息来进行调试。

在 Java 7 中,所有与反射操作相关的异常类都添加了一个新的父类 java.lang.ReflectiveOperationException,可以直接捕获这个新的异常。

内容总结

Java反射技术允许程序在运行时动态地获取类的信息、调用类的方法、访问类的属性等,从而提高程序的灵活性和可扩展性。它可以获取类的名称、包名、父类、接口、构造方法、方法、属性等信息,创建对象,调用方法,访问属性,实现动态代理等功能。Java反射技术在框架开发、ORM框架、动态代理、单元测试等方面都有着重要的应用。但是,由于使用反射技术需要额外的开销,因此在性能要求较高的场景下,应该尽量避免使用。

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

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

相关文章

【软件测试】

系列文章目录 文章目录 系列文章目录前言第四章 单元测试4.1 软件测试过程概述4.2 什么是单元测试4.2.1 单元测试的定义4.2.2 单元测试的重要性4.2.3 单元测试原则 4.3 单元测试的目标和任务4.3.1 单元测试的目标&#xff1a;单元模块被正确编码4.3.2 单元测试的主要任务 4.4 单…

FreeRTOS:事件标志组

目录 一、事件标志组简介1.1事件位(事件标志)1.2事件组1.3事件标志组和事件位的数据类型 二、创建事件标志组2.1函数 xEventGroupCreate()2.2函数xEventGroupCreateStatic() 三、设置事件位3.1函数 xEventGroupClearBits()3.2函数xEventGroupClearBitsFromISR()3.3函数 xEventG…

Python模块MarkupPy 自定义html报告

简介 MarkupPy是Python模块用于生成HTML和XML格式的字符串。它的主要作用是提供了一种比原生HTML/XML更加易读和易写的编写方式&#xff0c;通过Python代码来生成HTML或XML代码。 使用MarkupPy&#xff0c;可以在Python中使用不同的对象类型和方法&#xff0c;来动态地生成HTML…

做自动化测试老是失败?你真的会做吗?资深测试的总结整理...

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

揭开生成式人工智能的力量:60+医疗保健应用场景

预计生成式AI在医疗保健领域的增长速度将超过任何其他行业。在医疗技术领域&#xff0c;AI可带来更高效流程、个性化客户互动、更大的创新和更高价值。为了帮助领导者理解这些机会&#xff0c;BCG最近研究了医疗技术中生成式AI的60多个应用场景&#xff1a;从产研和软件开发到业…

Android自动化测试,5个必备的测试框架

Appium Appium是一个开源的移动测试工具&#xff0c;支持iOS和Android&#xff0c;它可以用来测试任何类型的移动应用&#xff08;原生、网络和混合&#xff09;。作为一个跨平台的工具&#xff0c;你可以在不同的平台上运行相同的测试。为了实现跨平台的功能&#xff0c;Appi…

关于数据库索引的入门简述

一、简介 数据库索引是现代数据库中高效数据检索的一个重要工具。它在优化查询性能和加快数据检索操作方面发挥着重要作用。这里我们深入了解下数据库索引其内部工作原理、优点和局限性。 二、数据库 1、SQL 数据库 为了理解索引&#xff0c;先说一句数据库&#xff0c;数据库…

Jenkins小技巧汇总

设置变量 设置全局环境变量 全局变量除了系统内置的全局环境变量之外&#xff0c;用户也可以设置全局变量。设置路径&#xff1a;【Dashboard】–>【Manage Jenkins】–>【System Configuration 下的 System】–>【Global properties】从描述中我们可以看到&#xf…

Jetson nano 之 ROS入门 - - 深度学习环境配置

文章目录 前言一、Anaconda安装二、Pytorch 与 TensorFlow 环境配置三、TensorRT 推理引擎配置总结 前言 Jetson Nano是一款由NVIDIA推出的小型计算机&#xff0c;其性能优异、功耗低、体积小巧&#xff0c;非常适合用于嵌入式系统和边缘设备的深度学习应用。Jetson Nano搭载了…

【Git原理与使用】-- 基本操作

目录 添加文件 查看objects中的文件 小结 修改文件 版本回退 回退的回退 小结 撤销修改 情况一&#xff1a;对于工作区的代码&#xff0c;还没有 add 情况二&#xff1a;已经 add &#xff0c;但没有 commit 情况三&#xff1a;已经 add &#xff0c;并且也 commit …

Cordic IP核使用说明以及避坑记录

Cordic IP核使用说明以及避坑记录 参考文章&#xff1a;(140条消息) Vivado cordic IP核rotate和translate使用详解(附有代码)_cordic ip核 rotate_迎风打盹儿的博客-CSDN博客 (140条消息) VIVADO cordic IP核_卡布奇诺加勺糖的博客-CSDN博客 文章目录 Cordic IP核使用说明以及…

面试题:推排序是一种稳定排序吗?

面试题&#xff1a;推排序是一种稳定排序吗&#xff1f; 在回答该问题前&#xff0c;首先需要了解什么是稳定排序。 稳定性就是指对于两个关键字相等的记录&#xff0c;它们在序列中的相对位置&#xff0c;在排序之前和排序之后没有发生改变。通俗地讲就是有两个关键字相等的…

Node.js---菜鸟教程

文章目录 创建第一个应用创建 Node.js 应用 NPM 使用介绍使用 npm 命令安装模块本地安装使用 package.json模块的操作 回调函数阻塞代码实例非阻塞代码 事件循环事件驱动程序 EventEmitterEventEmitter 类方法实例error 事件继承 EventEmitter Buffer&#xff08;缓冲区&#x…

Redis-缓存

新增或者更新数据时,创建以后顺便存到redis中去【维护缓存】 获取的时候先从redis缓存中拿数据 如果拿数据的时候为空,则到数据库中拿数据,后再存到redis缓存中去 大量的商品【包括冷门商品】都进行上面的缓存,那么就很耗内存 针对每个数据进行缓存的时候 维护一个过期时间…

MQTT(一)

MQTT&#xff08;一&#xff09; 1.背景 学习目标&#xff1a;经过了解&#xff0c;Netty占用服务器资源内存大、远距离传感器在极端条件下数据处理兼容较差&#xff08;网络条件差&#xff0c;需要反复重连等&#xff09;。从同行业了解到&#xff0c;现在主流工业传输使用M…

【博学谷学习记录】超强总结,用心分享 | 架构师 zabbix学习总结

文章目录 一、介绍zabbix zabbix专有词汇 二、zabbix zabbix实践修改zabbix zabbix语⾔服务器可视化指标解决zabbix zabbix乱码问题查看监控内容可视化监控agent agent的cpu cpu动态查看模板--监控项⾃定义监控项语法 一、介绍 Zabbix 是由 Alexei Vladishev 开发的⼀种⽹络监…

达梦数据库介绍

文章目录 前言一、达梦数据库的定位二、达梦有哪些工具1、达梦管理工具2、达梦数据迁移工具3、 达梦数据库配置助手4、其它工具 三、Linux下的工具1、数据库初始化工具2、数据库迁移工具3、其它工具 四、其它连接工具总结 前言 近几年由于各种原因&#xff0c;国内开启了一波国…

【Android】-- 如何对APP版本控制/更新?

目录 一、 前提准备 1、获取服务器 2、使用工具操作云服务器 二、Json格式网页 三、创建file_paths.xml及修改AndroidManifest.xml 四、在java代码加入更新检测代码 效果如图&#xff1a; 可以强制更新和非强制更新&#xff0c;和浏览器下载安装包。 一、 前提准备 1、获取…

0001-TIPS-2020-hxp-kernel-rop : ret2user

目的 理解系统调用的过程&#xff1a;从用户态进入内核态&#xff0c;再从内核态返回用户态。细节见文末的参考了解一般性提权方法commit_creds(prepare_kernel_cred (0)); 环境搭建 下载 pwn 2020-kernel-rop wget https://2020.ctf.link/assets/files/kernel-rop-bf9c106…

说精神力量的词,愿力很神奇

说精神力量的词&#xff0c;愿力最神奇&#xff01; ​愿力&#xff0c;心力&#xff0c;精神&#xff0c;精 气 神&#xff0c;气 &#xff0c;能量 【能量】是个外来词 趣讲大白话&#xff1a;200天了&#xff0c;布道的愿力推动我 【趣讲信息科技200期】 ******************…