前提

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

本文主要介绍反射中可能用到的两个比较特殊的类型,数组和枚举,分别对应java.lang.reflect.Arrayjava.lang.Enum,后者其实并不是反射类库包中的类,但是反射的基础类库里面有使用枚举类型的方法。

数组类型

数组是一种包含固定数量的相同类型组件(Component)的引用类型对象,也就是说数组的长度是不可变,它的每个元素都是相同类型的。创建数组实例需要定义数组的长度和组件的类型。数组是由Java虚拟机实现(这一点很重要,这就是为什么JDK类库中没有数组对应的类型的原因,array也不是Java中的保留关键字,操作数组的底层方法都是native方法),数组类型只有继承自java.lang.Object的方法,数组的length方法实际上并不属于数组类型的一部分,数组的length方法其实最终调用的是java.lang.reflect.Array#getLength(),注意到这个方法是native方法。java.lang.reflect.Array是基于反射操作数组的核心类。

使用非反射方式创建数组实例的过程如下:

fully_qualified_class_name[] variable_name  = {val1,val2,val3,...};

fully_qualified_class_name[] variable_name = new fully_qualified_class_name[${fix_length}];

例如:int[] arr = new int[10];

使用反射方式就是使用java.lang.reflect.Array中的相关方法:

Class<?> c = Class.forName(cName);
Object o = Array.newInstance(c, n);
for (int i = 0; i < n; i++) {
String v = cVals[i];
Constructor ctor = c.getConstructor(String.class);
Object val = ctor.newInstance(v);
Array.set(o, i, val);
}
Object[] oo = (Object[]) o;

下面列举一下java.lang.reflect.Array中的方法:

方法 功能
static Object newInstance(Class<?> componentType, int length) 指定组件类型和数组固定长度创建一维数组
static Object newInstance(Class<?> componentType, int... dimensions) 指定组件类型和多个固定长度创建多维数组,维度的最大值为255
static native int getLength(Object array) 获取数组长度
static native Object get(Object array, int index) 通过下标访问数组元素
static native void set(Object array, int index, Object value) 通过下标设置数组元素

这里省略了一部分对于IntBoolean等原始类型的Setter和Getter方法。

java.lang.Class和数组相关的方法:

方法 功能
native boolean isArray() 判断类型是否数组类型
Class<?> getComponentType() 如果是数组类型则返回其组件类型,否则返回null

这里举个例子加深下印象:

public class ArrayCreationMain {

	/**
* 这个是我们创建的最终目标数组
*/
private static String R = "java.math.BigInteger[] bi = {123,234,345}";
private static final String[] S = new String[]{"123", "234", "345"}; public static void main(String[] args) throws Exception {
Class<BigInteger> componentType = BigInteger.class;
Object arrayObject = Array.newInstance(componentType, 3);
for (int i = 0; i < S.length; i++) {
String each = S[i];
Constructor<BigInteger> constructor = componentType.getConstructor(String.class);
BigInteger value = constructor.newInstance(each);
Array.set(arrayObject, i, value);
}
Object[] result = (Object[]) arrayObject;
System.out.println(String.format("%s[] = %s", componentType, Arrays.toString(result)));
int length = Array.getLength(arrayObject);
System.out.println("Length = " + length);
for (int i = 0; i < length; i++) {
System.out.println(String.format("index = %d,value = %s", i, Array.get(arrayObject, i)));
}
Class<?> arrayObjectClass = arrayObject.getClass();
System.out.println("Is array type:" + arrayObjectClass.isArray());
System.out.println("Component type:" + arrayObjectClass.getComponentType());
}
}

运行后输出:

class java.math.BigInteger[] = [123, 234, 345]
Length = 3
index = 0,value = 123
index = 1,value = 234
index = 2,value = 345
Is array type:true
Component type:class java.math.BigInteger

需要注意的是,java.lang.reflect.Array中的Setter和Getter方法如果越界操作数组元素,会抛出ArrayIndexOutOfBoundsException,通过Setter设置和数组初始化时候的组件类型不一致的元素会抛出IllegalArgumentException

细议数组类型

前面说到了数组类型的一些基础特性,这里补充一些比较高级的使用方法。

创建特定元素类型的数组:

因为Java泛型擦除的问题,实际上我们使用Array#newInstance方法只能得到一个Object类型的结果实例,其实这个结果实例的类型就是ComponentType[],这里只是返回了它的父类(Object)类型实例,因此我们可以直接强转,例如:

String[] strArray = (String[]) Array.newInstance(String.class, 3);

获取数组类型:

在非反射方式下,我们可以通过数组实例.class通过class字面量直接获取数组类型,例如:

Class stringArrayClass = String[].class;

反射条件下,可以通过Class.forName()获取数组类型,但是调用此方法的时候有个限制,类名必须使用JVM可以识别的签名形式,就是[L${ComponentType};,注意Class.forName()无法获取原始类型(如int、boolean)的类型,例如:

// 不能漏了左边的[L和右边的;
Class stringArrayClass = Class.forName("[Ljava.lang.String;"); // 下面这样做会抛出ClassNotFoundException
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");

获取数组元素(组件)类型:

目前获取数组组件类型只能通过数组类型实例去调用Class#getComponentType()

枚举类型

枚举是一种语言结构(Language Construct),用于定义可以使用一组固定的名值对表示的类型安全的枚举(原文是:An enum is a language construct that is used to define type-safe enumerations which can be used when a fixed set of named values is desired)。所有枚举都继承自java.lang.Enum。枚举可以包含一个或者多个枚举常量,这些枚举常量都是该枚举的实例。枚举的声明其实和一个普通的Class的声明相似,因为它可以包含字段、方法和构造函数之类的成员。

因为枚举就是普通的Java类,因此反射相关类库中并没有添加一个java.lang.reflect.Enum类型,反射中的API和枚举相关的有:

  • boolean java.lang.Class#isEnum():判断类型是否枚举类型。
  • T[] java.lang.Class#getEnumConstants():获取类型中所有的枚举常量。
  • boolean java.lang.reflect.Field#isEnumConstant():判断属性是否枚举类型。

如果实例中的成员属性为枚举,那么枚举的反射操作实际上就是java.lang.reflect.Field的相关操作。

举个例子:

public class EnumerationMain {

	enum Color {
RED, BLACK, BLUE
} public static class ColorHolder { private Color color = Color.BLACK; } public static void main(String[] args) throws Exception {
Class<Color> colorClass = Color.class;
System.out.println("Color class is enum:" + colorClass.isEnum());
System.out.println("Color values:" + Arrays.toString(colorClass.getEnumConstants()));
ColorHolder colorHolder = new ColorHolder();
Class<ColorHolder> holderClass = ColorHolder.class;
Field field = holderClass.getDeclaredField("color");
field.setAccessible(true);
System.out.println("Old color:" + field.get(colorHolder));
field.set(colorHolder, Color.RED);
System.out.println("New color:" + field.get(colorHolder));
}
}

运行后输出:

Color class is enum:true
Color values:[RED, BLACK, BLUE]
Old color:BLACK
New color:RED

之前写过一篇文章《JDK中枚举的底层实现》,从枚举类的字节码翻译出类的代码逻辑,这里翻出来那个例子(手机操作系统枚举)说一下:

public enum PhoneOsEnum {

	/**
* 安卓
*/
ANDROID(1, "android"), /**
* ios
*/
IOS(2, "ios"); private final Integer type;
private final String typeName; PhoneOsEnum(Integer type, String typeName) {
this.type = type;
this.typeName = typeName;
} public Integer getType() {
return type;
} public String getTypeName() {
return typeName;
}
}

这个是我们使用Java的关于枚举的语法创建出来的枚举类型,是编译前我们看到的Java类文件,实际上,编译完成之后,枚举类型会变成一个普通的Java类,它有以下特点:

  • 1、枚举类型会变成一个普通Java类,这个Java类会继承java.lang.Enum,并且把自身类型作为泛型参数类型,构造函数中必定包含name(字符串类型String)、ordinal(整型int)参数,因为父类java.lang.Enum的构造要求传入这两个参数。
  • 2、所有的枚举成员属性都变成static final修饰的在第1步中提到的Java类的实例,属性的名称和原来枚举的名字一致,实例在静态代码块中创建。
  • 3、新增了一个static final修饰的第1步中提到的Java类的数组实例,名称为$VALUES,此数组在静态代码块中创建,基于此数组还新增了一个静态方法values(),此方法就是直接返回数组的克隆。

也就是上面提到的PhoneOsEnum在编译完成之后会变成:

public final class PhoneOsEnumeration extends Enum<PhoneOsEnumeration> {

	public PhoneOsEnumeration(String name, int ordinal, Integer type, String typeName) {
super(name, ordinal);
this.type = type;
this.typeName = typeName;
} public Integer getType() {
return type;
} public String getTypeName() {
return typeName;
} public static PhoneOsEnumeration[] values() {
return $VALUES.clone();
} public static PhoneOsEnumeration valueOf(String name) {
return Enum.valueOf(PhoneOsEnumeration.class, name);
} private final Integer type;
private final String typeName;
public static final PhoneOsEnumeration ANDROID;
public static final PhoneOsEnumeration IOS;
private static final PhoneOsEnumeration[] $VALUES; static {
ANDROID = new PhoneOsEnumeration("ANDROID", 0, 1, "android");
IOS = new PhoneOsEnumeration("IOS", 1, 2, "ios");
$VALUES = new PhoneOsEnumeration[]{ANDROID, IOS};
}
}

实际上,如果你直接编写一个Java类去继承java.lang.Enum会编译报错,也就是Java希望把枚举的行为和特性交由自身控制而不是开发者去控制,从编译层面控制枚举的类型安全。如果细心一点会发现,枚举中valueOf(String name)也是由java.lang.Class提供的,追溯到最里层是T[] java.lang.Class#getEnumConstants()方法,其实有可能在构造$VALUES属性的时候也是用到这个方法,这一点就没有深究下去,编译层面的东西可能会牵涉很多方面的知识,还没有到达那种水平。

小结

数组和枚举在Java中的使用频率也是比较高的,特别是算法或者框架中,本文尝试从反射角度介绍这两个类型的使用方式,掌握它们对数组或者枚举的使用有很大的帮助。

个人博客

(本文完 e-a-20181204)

深入分析Java反射(二)-数组和枚举的更多相关文章

  1. 深入分析Java反射(五)-类实例化和类加载

    前提 其实在前面写过的<深入分析Java反射(一)-核心类库和方法>已经介绍过通过类名或者java.lang.Class实例去实例化一个对象,在<浅析Java中的资源加载>中也 ...

  2. 深入分析Java反射(一)-核心类库和方法

    前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...

  3. 深入分析Java反射(八)-优化反射调用性能

    Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Deb ...

  4. Java反射之数组的反射应用

    上一篇我们说了Java反射之成员方法的反射 这一篇我们说一说数组的反射应用,数组的有长度等属性,所以也会有相应的方法获得这些属性,这里我们不一一列举哪些方法.我们来了解反射包中的一个类----Arra ...

  5. Java反射遍历数组

    日志中有时候需要查看数组中的值,但是重载很多的打印函数,觉得很别扭.所以想通过反射,获取数组中的值,打印出来.Java提供了数组反射操作的类,之前没有关注过,提供的方法简单易用. public sta ...

  6. 深入分析Java反射(三)-泛型

    前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...

  7. 深入分析Java反射(六)-反射调用异常处理

    前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...

  8. 深入分析Java反射(七)-简述反射调用的底层实现

    前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...

  9. 深入分析Java反射(四)-动态代理

    动态代理的简介 Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分 ...

随机推荐

  1. C语言里面和时间有关的函数

    参考链接 https://blog.csdn.net/ffcjjhv/article/details/83376767 0)Head file #include "time.h" ...

  2. IPv6-isis配置

    ①:ipv6 unicast-routing——开启IPv6路由功能 ②:router isis word——开启ISIS进程 ③:is-type——可以修改路由器ISIS等级 ④:进入接口 ⑤:启用 ...

  3. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring事务管理接口PlatformTransactionManager、TransactionDefinition和TransactionStatus

    Spring 的事务管理是基于 AOP 实现的,而 AOP 是以方法为单位的.Spring 的事务属性分别为传播行为.隔离级别.只读和超时属性,这些属性提供了事务应用的方法和描述策略. 在 Java ...

  4. Python MySQL 删除表

    章节 Python MySQL 入门 Python MySQL 创建数据库 Python MySQL 创建表 Python MySQL 插入表 Python MySQL Select Python M ...

  5. Elasticsearch 集群 - 健康检查

    章节 Elasticsearch 基本概念 Elasticsearch 安装 Elasticsearch 使用集群 Elasticsearch 健康检查 Elasticsearch 列出索引 Elas ...

  6. spring boot配置druid连接池连接mysql

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  7. vue学习(二)Vue常用指令

    2 Vue常用指令 1. vue的使用要从创建Vue对象开始 var vm = new Vue(); 2. 创建vue对象的时候,需要传递参数,是json对象,json对象对象必须至少有两个属性成员 ...

  8. K8S Kubernetes 简单介绍 转自 http://time-track.cn/kubernetes-trial.html Kubernetes初体验

    这段时间学习了一下 git jenkins docker  最近也在看  Kubernetes  感觉写得很赞  也是对自己对于K8S 有了进一步得理解  感谢 倪 大神得Blog 也希望看到这篇Bl ...

  9. UVA - 818 Cutting Chains(切断圆环链)(dfs + 二进制法枚举子集)

    题意:有n个圆环(n<=15),已知已经扣在一起的圆环,现在需要打开尽量少的圆环,使所有圆环可以组成一条链. 分析:因为不知道要打开哪个环,如果列举所有的可能性,即枚举打开环的所有子集,最多才2 ...

  10. c++ opencv 动态内存

    1.CvMemStorage定义动态内存存储器   内存存储器是一个用来存储诸如序列.轮廓.图形和子划分等动态增长数据结构的底层结构 2.示例 CvMemStorage *mems = cvCreat ...