@

带你攻破你很可能存在的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框架、动态代理、单元测试等方面都有着重要的应用。但是,由于使用反射技术需要额外的开销,因此在性能要求较高的场景下,应该尽量避免使用。

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

  1. 「编程羽录」上线,程序员必备的这些技能你能get到嘛?

    大家好,我是小羽. 好久不见,给大家带来个好消息,小羽的全新专题「编程羽录」系列正式上新,主要是介绍一些关于面试题和经验总结的文章. 会为大家提供一些技术栈之外,程序员还需要的其他方面硬核知识,做到全 ...

  2. ☕【JVM技术指南】「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"【下部】

    承接上文 (完结撒花1-52系列)[JVM技术指南]「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"[上部] 并行收集器 并行收集器(也称为吞吐量收集器)是类似 ...

  3. 2023 年该学点什么技术?「GitHub 热点速览 v.23.03」

    春节期间,小鱼干读了一篇万字回顾数据库行业的文章,在文字缝隙里我看见了两个词:AI+ 和数据两个词(当然数据是废话,毕竟是一个数据库的回顾文).在 GitHub 上热点趋势上,可见到 AI+ 的身影, ...

  4. 报名|「OneAPM x DaoCloud」技术公开课:Docker性能监控!

    如今,越来越多的公司开始 Docker 了,「三分之二的公司在尝试了 Docker 后最终使用了它」,也就是说 Docker 的转化率达到了 67%,同时转化时长也控制在 60 天内. 既然 Dock ...

  5. 「JavaSE 重新出发」01. Java介绍

    「白皮书」关键术语 简单性(C++--) 面向对象 分布式 健壮性 安全性 体系结构中立 可移植性 解释型 高性能 多线程 动态性 Java 发展历程 SUN公司--Stanford Universi ...

  6. 黑马程序员:Java编程_反射技术

    =========== ASP.Net+Android+IOS开发..Net培训.期待与您交流!=========== Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性 ...

  7. java 反射技术

    什么是反射?反射就是将字节码中的各种成分映射到相应的java类中来,java反射技术自JDK1.1以来就出现了,目前大多数流行的框架都采用了这种技术,可见其重要性,这篇文章将详细介绍我对java反射技 ...

  8. Android中Java反射技术的使用示例

    import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Metho ...

  9. 反射那些事儿——Java动态装载和反射技术

    一直以来反射都是只闻其声,却无法将之使用,近日尽心下来学习下,发现了很多精妙之处. Java动态装载和反射技术 一.类的动态装载 1.Java代码编译和执行的整个过程包含了以下三个重要的机制: ● J ...

  10. Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框

    原文出处:博主宇宙的极客http://www.cnblogs.com/nokiaguy/archive/2010/07/27/1786482.html 众所周知,AlertDialog类用于显示对话框 ...

随机推荐

  1. 明解STM32—GPIO理论基础知识篇之寄存器原理

    ​ 一.前言 在之前的STM32的GPIO理论基础知识中,分别对基本结构和工作模式进行了详细的介绍.GPIO基本结构中主要对GPIO内部的各个功能电路逐一的进行的分析:GPIO工作模式中主要介绍GPI ...

  2. 实现和CSS一样的easing动画?直接看Mozilla、Chromium源码!

    前言 在上一篇丝滑的贝塞尔曲线:从数学原理到应用介绍贝塞尔曲线实现动画时给自己留了一个坑,实现的动画效果和CSS的transition-timing-function: cubic-bezier差别较 ...

  3. TOP使用参数

    TOP使用参数top是检查机器当前运行状况的第一个命令,就好比是机器体检时的第一张报告单.先了解一下TOP命令的使用 [root@localhost /]# top -help top: procps ...

  4. it必给装机小软件附源码

    需要的包 启动之后是这个样子的 远吗如下: #authon fengimport zipfile as zfimport osimport win32apiimport win32conimport ...

  5. 轻量级Web框架Flask(二)

    Flask-SQLAlchemy MySQL是免费开源软件,大家可以自行搜索其官网(https://www.MySQL.com/downloads/) 测试MySQL是否安装成功 在所有程序中,找到M ...

  6. pyinstaller打包python程序

    pyinstaller打包python程序 1.pyinstaller安装 安装命令: #升级pip版本 >>> pip install -U pip #安装pyinstaller ...

  7. If选择语句的用法

    今天我们学习下If判断语句. 首先了解下它有几种用法: If单选择语句 If双选择语句 If多选择语句 我们一个一个用,每一个用法都给一个运用的过程演练一下. If单选择语句:我们很多需要判断一个东西 ...

  8. App复杂动画实现——Rive保姆级教程

    作者:京东物流 沈明亮 在App开发过程中,如果想实现动画效果,可以粗略分为两种方式.一种是直接用代码编写,像平移.旋转等简单的动画效果,都可以这么干,如果稍微复杂点,就会对开发工程师的数学功底.图形 ...

  9. pytest的几种执行方式

    1 pytest xxxx 2 python -m pytest xxxx python -m pytest --html=./report/rep2.html test_env_pytest_ini ...

  10. CentOS 8 部署 ELK 8.7真的是方便呀

    之前装过一次 ELK 7.7,相比之下装 8.7可方便太多了~ CentOS版本 CentOS-8.5.2111-x86_64-dvd1 JAVA ELK会自己使用内置版本的JDK ElasticSe ...