编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

阿里技术大牛;详解Java反射操作 java反射的用处

gowuye 2024-04-04 11:58 13 浏览 0 评论

享学课堂特邀作者:逐梦々少年

转载请声明出处!

前言

java开发过程中,经常使用一些框架如Spring、SpringMvc来简化开发操作,在web开发中,我们可以利用SpringMvc快速的把请求参数和实体进行转换,在使用数据库操作的时候,可以使用Mybatis/hibernate等框架实现实体与db的转换操作,这其中就是利用了java的反射机制实现的操作,所以java进阶之旅一定会有反射一席之地,下面让我们详细学习一下Java反射相关的类与方法

反射适合用在哪

首先我们先思考一个问题,反射适合使用在哪里呢?从功能上看,反射似乎无所不能,几乎所有的类,所有的属性、方法、构造我们都能使用,但是我们细细思考一下,在实际开发中,并不是所有场景都需要使用反射获取属性或者方法进行操作,反而更多的使用实例.xxx方式操作,而当这些操作重复次数较多的时候,我们往往会考虑优化代码,减少代码冗余,提高复用,比如实体构建赋值等操作,这个时候往往是我们最需要复用的地方,所以我们可以大体认为反射主要使用在实体操作过程中。而在一般操作数据的过程中,我们的实体一般都是知道并且依赖于对应的数据类型的,比如:

  • 1.根据类型new的方式创建对象
  • 2.根据类型定义变量,类型可能是基本类型也可能是引用类型、类或者接口
  • 3.将对应类型的对象传递给方法
  • 4.根据类型访问对象的属性,调用方法等操作

以上这些操作都是数据操作的过程中最常见也是最难复用优化的地方,而如果这里的操作使用反射则可以实现动态的操作不同的类型的实例,通过调用反射入口类Class,获取对应的属性、构造、方法完成对应的操作,所以接下来我们先从Class入口开始学习

获取Class

学习过类和继承实现原理的时候,我们都知道,每个已经加载的类在内存中都有一份对应的类信息,而每个对象都有所属类的引用。其中类信息存放的类即为java.lang.Class。而所有类的基类Object中就有一个本地方法可以快速获取到Class对象

public final native Class<?> getClass()

可以看到返回的类型为Class<?>,即为当前类的信息,但是由于基类的子类并不明确,所以具体的类型这里使用范型的方式返回.

getClass方法不仅仅可以使用在类中,针对于接口也可以使用,当然接口没有具体的实现,所以不可能是接口的实例.getClass的方式获取,这个时候我们就需要调用内置的.class属性快速获取类型:

Class<Comparable> cls = Comparable.class;

而在java中有一些特殊的类型,例如基本类型,还有void类型,这种类型无法直接创建实例,如果要获取class,与接口的方式相同,如下:

//这里可以看出来,基本类型的class即为对应的包装类型
Class<Integer> intCls = int.class;
Class<Byte> byteCls = byte.class;
Class<Character> charCls = char.class;
Class<Double> doubleCls = double.class;
//void也是一种特殊的类型,返回的也是对应的包装类Void
Class<Void> voidCls = void.class;

而数组和枚举作为java中的特殊实现类,获取的class类型也是较为特殊的,数组具有维度特性,所以获取的class类型同样具有维度,即一维数组有一个,二维数组有两个,每个维度都有对应的不同类型,而枚举的class则是其中定义的每一个子集,如下:

String[] strArr = new String[10];
int[][] twoDimArr = new int[3][2];
int[] oneDimArr = new int[10];
Class<? extends String[]> strArrCls = strArr.getClass();
Class<? extends int[][]> twoDimArrCls = twoDimArr.getClass();
Class<? extends int[]> oneDimArrCls = oneDimArr.getClass();

而通过上述方式获取Class对象以后,我们就可以了解到关于类型的很多信息,并且基于这些信息可以获取我们想要的详细信息,大致可以分为以下几类,包括名称信息、字段信息、方法信息、创建对象的方法、构造信息与类型信息等,接下来我们就分别学习这几类信息相关的内容

Class名称信息

我们在开发过程中,获取到Class以后,往往需要获取对应的类名进行操作,比如比较类名,排除类名等操作,而在Class类中,提供了以下几个方法获取类名称相关信息:

public String getName()
public String getSimpleName()
public String getCanonicalName()
public Package getPackage()

这里可以看出分别能获取四种不同的类名称,那么获取的具体内容有什么不同呢?我们先看一张罗列的表格信息:

可以看出来各个不同类型的Class通过四种方法获取的类名称信息完全不同,那么为什么会出现这么大的区别呢?这里我们需要注意的是:

getName()

getName()方法获取的是标准的类名称信息,即Java内部真正的类型对应的名称信息(JVM真实的类名称信息),这里我们可以看出来数组的getName()的结果为[I,这里需要解释一下,[表示的是数组,并且和维度有关系,如果是二维数组,那么这里就会是[[,而后面的I则是int类型在JAVA中真实的类型的简写,八大基本类型对应的类简写名称如下所示:

而这里还有一点需要注意的是,如果是引用类型对象的数组,Class的真实类名结尾还会有一个分号;

getSimpleName()

getSimpleName()方法是jdk实现的用来快速获取当前类的真实类名的路径缩写,即不带包名的Class名称

getCanonicalName()

getCanonicalName()方法获取到的即为java中的完整伪类名,即包名+getSimpleName()名称的完整名称,此方法获取的Class名称比较友好

getPackage()

getPackage()则是java中默认实现的可以用来快速获取当前Class所在包名的方法,此方法仅仅返回类路径的前置(包名),不包含类名

Class字段信息

获取完Class的名称信息以后,我们开始关注如何通过Class类获取类属性信息。我们知道在类中定义的静态和实例变量都被称为字段,在Class类中则是使用Field表示,位于java.lang.reflect包下,而在Class类中有如下方法可以获取Field信息:

public Field[] getFields()
public Field[] getDeclaredFields()
public Field getField(String name)
public Field getDeclaredField(String name)

可以看出来主要是分为两类,一类返回的是字段数组,一类返回具体的字段,分别看下这两类方法的作用:

getFields()/getDeclaredFields()

getFields()/getDeclaredFields()方法都是返回的当前Class中所有的字段,其中包括来自父类的,但是这两个方法在使用的时候有一定的区别,getFields()方法只能返回非私有的字段,而getDeclaredFields()则是返回所有的字段,包括私有的字段,当然还需要借助setAccessible方法才能实现

getField(String name)/getDeclaredField(String name)

getField(String name)/getDeclaredField(String name)这两个方法都是通过字段名来获取对应的字段信息,通过命名我们不难发现,和上一组类似,getField(String name)方法只能从所有的非私有的字段中查找当前名称的字段信息,getDeclaredField(String name)则是从所有的字段中查找对应名称的字段信息

操作Field的常见方法

通过上述的四个方法我们可以轻松获取到Class中对应的字段信息,接下来我们只要对这些信息进行操作处理,即可完成我们要做的操作,而常用的操作Field的方法如下:

public String getName()
public boolean isAccessible()
public void setAccessible(boolean flag)
public Object get(Object obj)
public void set(Object obj, Object value)

getName()

getName()方法通过命名即可看出来,此方法可以获取到当前Field的字段名

isAccessible()

isAccessible()方法我们在上述getDeclaredFields()方法的时候曾经介绍过,如果需要获取私有字段,需要setAccessible方法支持,此方法则是可以获取是否获取到setAccessible方法的支持,即是否支持获取私有字段

setAccessible(boolean flag)

setAccessible(boolean flag)则是通过设置boolean值确认当前反射获取Field的操作中,是否检查私有字段,设置为true,则不检查,反射可以获取到私有Field,设置为false则是检查私有字段,反射不可获取私有Field

get(Object obj)/set(Object obj, Object value)

get(Object obj)/set(Object obj, Object value)方法我们都不陌生,这一对方法则是能对当前Field设置对应的值/获取对应的值

其他方法

除了上述常见的方法以外,开发过程中可能还会使用到一些其他操作Field的方法,搭配使用,可以实现更灵活的字段操作,方法如下:

//返回当前字段的修饰符--public、private等
public int getModifiers()
//返回当前字段的类型--String等
public Class<?> getType()
//通过当前方法获取/赋值基础类型的字段值
public void setBoolean(Object obj, boolean z)
public boolean getBoolean(Object obj)
public void setDouble(Object obj, double d)
public double getDouble(Object obj)
//获取当前字段上的注解,使用jpa或者mybatis-plus等框架的时候,会添加在字段上一些注解
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
public Annotation[] getDeclaredAnnotations()

Class方法信息

获取完Field字段信息以后,往往我们还需要进行方法的操作,比如调用xx方法实现部分功能,这个时候就需要获取方法信息,而在Class中提供了很多操作方法信息的方法,常见的如下:

//获取所有的非私有方法,包括父类的非私有方法
public Method[] getMethods()
//获取所有方法,包括私有方法和父类的非私有方法
public Method[] getDeclaredMethods()
//从当前Class的所有public方法中查找对应名称,并且参数列表相同的方法(包括父类非私有方法)
//如果查找不到会抛出NoSuchMethodException异常
public Method getMethod(String name, Class<?>... parameterTypes)
//从当前Class的所有方法包括父类的非私有方法中查找对应名称并且参数列表相同的方法,
//如果查找不到会抛出NoSuchMethodException异常
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

而当我们通过上述方法获取到Method对象后,即可操作此对象完成Method方法调用等,而Method信息包含如下内容:

//获取当前Method的名称
public String getName()
//是否忽略检查机制,允许调用私有的Method,如果设置为true,则忽略检查,允许调用私有方法,
//设置false则使用检查机制,不允许操作私有方法
public void setAccessible(boolean flag)
//调用指定obj实例对象的当前方法,并且依据参数调用正确的方法
public Object invoke(Object obj, Object... args) throws
IllegalAccessException, Illegal-ArgumentException, InvocationTargetException

反射创建实例与构造方法

当我们拿到了字段信息和方法信息以后,这儿时候我们基本已经可以操作这些完成很多常见的功能了,但是除此之外,日常开发中还会遇到构造实例的频繁操作,如果能反射创建实例就好了,所以Class中提供了构建实例的方法,并且提供了操作Class的构造方法,如下:

//获取当前Class的所有public构造方法列表
public Constructor<?>[] getConstructors()
//获取当前Class中所有构造方法的列表,包含private的构造
public Constructor<?>[] getDeclaredConstructors()
//根据参数列表查找当前符合的非私有构造方法,不满足的情况抛出NoSuchMethodException异常
public Constructor<T> getConstructor(Class<?>... parameterTypes)
//根据参数列表查找当前所有构造中符合的方法,不满足的情况抛出NoSuchMethodException异常
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

通过调用获取的构造方法可以完成类实例的加载构建,同样的我们也可以使用如下方法快速构建类实例:

public T newInstance() throws InstantiationException, IllegalAccessException

举个简单的例子:

Map<String,Integer> map = HashMap.class.newInstance();//通过Class<?>.newInstance构建类实例
map.put("hello", 123);

Class的类型信息与声明信息

通过上述的学习,我们已经了解到Class类是个神奇的存在,既可以获取名称,也可以操作字段和方法,那么我们不禁疑惑,Class代表的类型到底是普通的类,还是内部类,还是基础类型或者数组呢?其实Class是一个类型的集合,每一个Class的类型取决于原类型,在Class方法中也提供了如下方法辅助判断Class的类型信息,如下:

//Class类型是否为数组
public native boolean isArray() 
////Class类型是否为基本数据类型--包装类
public native boolean isPrimitive() 
//Class类型是否为接口
public native boolean isInterface()
//Class类型是否为枚举
public boolean isEnum() 
//Class类型是否为注解类型
public boolean isAnnotation() 
//Class类型是否为匿名内部类
public boolean isAnonymousClass() 
//Class类型是否为成员类,定义在方法外的Class
public boolean isMemberClass()
//Class类型是否为本地类,即定义在方法内的Class,非匿名内部类
public boolean isLocalClass() 

除了Class本身的类型信息,我们也可以根据以下方法获取Class的声明信息、父类、接口等信息,方法如下:

//获取当前类的修饰符
public native int getModifiers()
//获取当前类的父类型信息
public native Class<? super T> getSuperclass()
//获取当前类实现的所有的接口信息
public native Class<?>[] getInterfaces();
//获取当前类申明的注解信息数组
public Annotation[] getDeclaredAnnotations()
//获取当前类中所有的注解信息
public Annotation[] getAnnotations()
//根据注解的完整类名,查找到对应的注解信息
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)

数组与枚举的反射

在使用反射过程中,除了日常的操作以外,有些时候我们还需要针对数组和枚举类型的Class做一些反射操作,而数组类型的操作往往需要借助java.lang.reflect包下的Array类操作完成,主要方法如下:

//创建元素类型、元素长度指定的数组
public static Object newInstance(Class<?> componentType, int length)
//创建多维度的数组,dimensions可连续传递多个,分别代表不同维度
public static Object newInstance(Class<?> componentType, int... dimensions)
//获取指定数组的对应索引的值
public static native Object get(Object array, int index)
//赋值给指定数组的对应索引下的值
public static native void set(Object array, int index, Object value)
//获取数组长度
public static native int getLength(Object array)

需要注意的是,在Array类中,数组使用Object而不是Object[]表示,这是为了方便处理多种类型的数组而设计的,因为在javaint[]String[]等数组都不可以与Object[]相互转化,但是却可以转为Object,例如:

int[] intArr = (int[])Array.newInstance(int.class, 10);
String[] strArr = (String[])Array.newInstance(String.class, 10);

除了数组类型,在开发中,尤其是遇到固定常量类型的时候,往往选择使用枚举类来实现操作,但是在反射中,当我们需要查找枚举类型的时候,Class类提供了如下方法获取我们枚举类中定义的所有的常量,从而可以实现枚举相关的反射操作

//获取当前枚举类型Class的所有定义的枚举常量
public T[] getEnumConstants()

如果喜欢请点赞、收藏+关注哦~

你的关注是我们更新文章最大的动力~

关注账号查看更多往期内容,并且获取第一时间更新资讯!

相关推荐

嵌入式C语言中常量的应用实例(嵌入式c语言中常量的应用实例是什么)

常量,我们都知道,就是数值保持不变的量。在C语言中,常量一旦初始化了,它的值将在整个程序运行周期内,不允许发生任何变化。常量与变量是相对的,我们实际项目中经常会用到它。定义常量的两种方式C语言中主要有...

C语言编程基础知识汇总学习,适合初学者!更新常量知识

(二)整型常量整型常量有3种形式:十进制整型常量、八进制整型常量和十六进制整型常量。(注意:c语言中没有直接表示二进制的整型常量,在c语言源程序中不会出现二进制。)书写方式如下:十进制整型常量:123...

【C语言】第二章第六节:字符串常量

第二章第六节:字符串常量。下表C语言中的常用转义字符。·字符形式功能:ASCIl码(十进制形式)。→\t水平制表(横向跳格:跳到下一个tab位置)。→\b退格8。→\r回车(不换行,光标移到本行行首)...

「GCTT 出品」Go 系列教程——5. 常量

这是我们Golang系列教程的第五篇。定义在Go语言中,术语”常量”用于表示固定的值。比如5、-89、IloveGo、67.89等等。看看下面的代码:varaint=50v...

每日C语言-常量指针、指针常量、指向常量的指针常量

一、常量指针1)什么是常量指针?通过该指针不可以修改其所指向存储单元中的值指针本身即地址可以被修改2)定义:类型说明符const*指针变量;类型说明符表示指针所指向存储单元中的值得数据类型指针...

C语言-符号常量、常变量、变量之我见

更新内容:新增音频。音频和文章一起更配oHello,大家好,又和大家见面了~~相信很多朋友们听了C语言的“符号常量”、“常变量”、“变量”后还是对这三者一脸懵逼吧。不管老师怎么歇斯底里地讲解,同学们迷...

零基础带你学习C语言:四:探索常量与变量

前言常量与变量学习;一:分析:short、float、long类型#include<stdio.h>intmain(){shortage=18;floatweight=12...

C语言中是如何定义常量的?那定义字符串呢?

常量有整型常量、浮点型常量、字符型常量及字符串常量。‘常量定义是指定义符号常量,用一个标识符来代表一个常量,通过宏定义预处理指令来实现。常量的定义:#definecount60这就定义了一个常量...

C语言符号常量的优点,会是那几点?

符号常量是一个常量,是不变量,所以,在编译的时候,就把符号常量出现的地方,替换为符号常量对应的常量。符号常量一般用户定义一个全局使用的数据,而且要改变该数据的时候,只需要改变符号常量的值,代码中引用符...

嵌入式开发- C语言数据类型-常量(c语言嵌入式是干嘛的)

基本数据类型的常量-掌握**整型常量:**常量是指在程序运行期间其数值不发生变化的数据。整型常量通常简称为整数整数可以是十进制数、八进制数、十六进制数八进制06334十六进制0xd1...

c语言解剖课:只读变量、常量、字面量傻傻分不清?

写在前面本篇主题的缘起,是因为一个计算机专业的大学生在和我讨论c语言问题时,说const常量如何如何,我说变量被const修饰了,还是变量,不是“常量”。他给了我一个截图:他说大模型都是这样回答的,变...

C/C++编程笔记:C数组、字符串常量和指针!三分钟弄懂它

想弄懂C语言中数组和指针的关系吗?这篇文章就占据你三分钟时间,看完你肯定会有收获!数组数组声明为数据类型名称[constant-size],并将一个数据类型的一个或多个实例分组到一个可寻址的位...

C语言入门到精通【第008讲】——C语言常量

C语言常量常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。常量就像是常规的变量,只不过常...

这是C语言无法修改得东西,C语言基础教程之常量解析

常量是指程序在执行期间不会改变的固定值。这些固定值也称为文字。常量可以是任何基本数据类型,如整数常量,浮点常量,字符常量或字符串文字,还有枚举常量。常量被视为常规变量,除了它们的值在定义后无法修改。整...

C语言中的单精度、双精度、常量等都有什么意思?

刚接触C语言时,对于常量,变量,浮点,单精度,双精度等问题的理解,大都很模糊不清,其实在程序运行过程中,其值不能改变的量称为常量。如12、0、-3为整型常量,4.6、-1.23为实型常量,'a'、'...

取消回复欢迎 发表评论: