反射三步曲之三反射数组和枚举

数组

数组类型是一个引用类型的对象,它包含了不可变的长度和相同的类型。创建一个数组需要知道长度和组件类型。组件可以使基本类型和引用类型。多维数组其实就是数组包含了数组

数组由Java虚拟机实现,数组的方法继承于Object,数组的长度不是其类型的一部分可以通过 java.lang.reflect.Array.getLength()来获取。

反射提供了访问数组类型和数组组件类型的方法,创建一个新的数组,获取或者设置数组的的组件值。下面就来跟我一起走进反射的数组吧

确定数组类型

tip:数组可以通过调用Class.isArray()方法来获取。

下面一个例子列出了数组的组件类型和里面的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ArrayFind {
public static void main(String... args) {
boolean found = false;
try {
Class<?> cls = Class.forName(args[0]);
Field[] flds = cls.getDeclaredFields();
for (Field f : flds) {
Class<?> c = f.getType();
if (c.isArray()) {
found = true;
out.format("%s%n"
+ " Field: %s%n"
+ " Type: %s%n"
+ " Component Type: %s%n",
f, f.getName(), c, c.getComponentType());
}
}
if (!found) {
out.format("No array fields%n");
}

} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}

执行方法

1
ArrayFind.main("java.nio.ByteBuffer");

得到结果

1
2
3
4
I/System.out: final byte[] java.nio.ByteBuffer.hb
I/System.out: Field: hb
I/System.out: Type: class [B
I/System.out: Component Type: byte

可以看到我们获取了一个字段 [B B是byte [标识数组

创建数组

类似于非反射代码,反射支持通过java.lang.reflect.Array.newInstance(). 动态创建任意类型的任意长度数组。

下面来写一个创建数组的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ArrayCreator {
private static String s = "java.math.BigInteger bi[] = { 123, 234, 345 }";
private static Pattern p = Pattern.compile("^\\s*(\\S+)\\s*\\w+\\[\\].*\\{\\s*([^}]+)\\s*\\}");

public static void main(String... args) {
Matcher m = p.matcher(s);

if (m.find()) {
String cName = m.group(1);
String[] cVals = m.group(2).split("[\\s,]+");
int n = cVals.length;

try {
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;
out.format("%s[] = %s%n", cName, Arrays.toString(oo));

} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
}
}
}
}

我们从上面的例子中可以看到几个方法首先:,Array.newInstance(c, n)其实是调用了Array的构造方法创建了一个Array,然后调用java.math.BigInteger的构造函数创建一个BigInteger,最终调用Array.set(o, i, val);来给创建的实例来添加成员

get或者set数组

首先介绍存取数组的方法,设置整个数组的方法是java.lang.reflect.Field.set(Object obj, Object value),拿数组的方法是 Field.get(Object)也可以用java.lang.reflect.Array.中的方法设置或去除数组。其提供了设置基本类型的方法,如设置int,就用Array.setInt(Object array, int index, int value)取呢就用Array.getInt(Object array, int index).引用类型呢?提供的方法是Array.set(Object array, int index, int value)Array.get(Object array, int index).来存取。

设置数组类型的字段

下面的例子替换BufferReader为更大,其构造方法可以设置大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class GrowBufferedReader {
private static final int srcBufSize = 10 * 1024;
private static char[] src = new char[srcBufSize];
static {
src[srcBufSize - 1] = 'x';
}
private static CharArrayReader car = new CharArrayReader(src);

public static void main(String... args) {
try {
//创建一个rander
BufferedReader br = new BufferedReader(car);
//获取cb
Class<?> c = br.getClass();
Field f = c.getDeclaredField("cb");
//因为字段cb是private所以获取权限
f.setAccessible(true);
//转换
char[] cbVal = char[].class.cast(f.get(br));
//将数组替换为更大的数组
char[] newVal = Arrays.copyOf(cbVal, cbVal.length * 2);
if (args.length > 0 && args[0].equals("grow"))
f.set(br, newVal);

for (int i = 0; i < srcBufSize; i++)
br.read();

if (newVal[srcBufSize - 1] == src[srcBufSize - 1])
out.format("新的数组, size=%d%n", newVal.length);
else
out.format("原始数组, size=%d%n", cbVal.length);

} catch (FileNotFoundException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (IOException x) {
x.printStackTrace();
}
}
}

可以看到我们先从从类中拿到了其缓存的数组,然后对其数组进行重新设置大小

设置多维数组

怎么理解多维数组?二维数组就是一维数组的数组,三维数组就是二维数组的数组,以此类推,下面我们举例来说明怎么创建多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.reflect.Array;
import static java.lang.System.out;

public class CreateMatrix {
public static void main(String... args) {
Object matrix = Array.newInstance(int.class, 2, 2);
Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);

Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);

for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
out.format("matrix[%d][%d] = %d%n", i, j, ((int[][])matrix)[i][j]);
}
}

当我们执行main方法得到

1
2
3
4
matrix[0][0] = 1
matrix[0][1] = 2
matrix[1][0] = 3
matrix[1][1] = 4

使用下面的方法可以得到同样的结果

1
2
3
4
5
6
7
8
9
10
11
Object matrix = Array.newInstance(int.class, 2);
Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);

Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);

Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);

Array.newInstance(Class componentType, int... dimensions) 提供了创建多维数组的方法,但是原理其实一样的,就是数组嵌套数组。

枚举

获取枚举的成员

反射提供了枚举所特定的API

Class.isEnum()表示是否为枚举类型。

Class.getEnumConstants()获取枚举的枚举顺序。

java.lang.reflect.Field.isEnumConstant()表示此字段是否表示枚举类型的元素。

我们来举例获取枚举的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Arrays;
import static java.lang.System.out;

enum Eon { HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC }

public class EnumConstants {
public static void main(String... args) {
try {
Class<?> c = (args.length == 0 ? Eon.class : Class.forName(args[0]));
out.format("Enum name: %s%nEnum constants: %s%n", c.getName(), Arrays.asList(c.getEnumConstants()));
if (c == Eon.class)
out.format(" Eon.values(): %s%n", Arrays.asList(Eon.values()));

} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}

当我输入EnumConstants.main("java.util.concurrent.TimeUnit"),可以得到

1
2
3
4
Enum name:  java.util.concurrent.TimeUnit
Enum constants: [NANOSECONDS, MICROSECONDS,
MILLISECONDS, SECONDS,
MINUTES, HOURS, DAYS]

当输入EnumConstants.main()可以得到:

1
2
3
4
Enum constants:  [HADEAN, ARCHAEAN, 
PROTEROZOIC, PHANEROZOIC]
Eon.values(): [HADEAN, ARCHAEAN,
PROTEROZOIC, PHANEROZOIC]

那么我们可以知道Arrays.asList(c.getEnumConstants())Arrays.asList(Eon.values())返回的东西是一样的

set或者get枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.lang.reflect.Field;
import static java.lang.System.out;

enum TraceLevel { OFF, LOW, MEDIUM, HIGH, DEBUG }

class MyServer {
private TraceLevel level = TraceLevel.OFF;
}

public class SetTrace {
public static void main(String... args) {
TraceLevel newLevel = TraceLevel.valueOf(args[0]);

try {
MyServer svr = new MyServer();
Class<?> c = svr.getClass();
Field f = c.getDeclaredField("level");
f.setAccessible(true);
TraceLevel oldLevel = (TraceLevel)f.get(svr);
out.format("Original trace level: %s%n", oldLevel);

if (oldLevel != newLevel) {
f.set(svr, newLevel);
out.format(" New trace level: %s%n", f.get(svr));
}

} catch (IllegalArgumentException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}

其实就跟设置字段的值是一样的!创建成功然后设置就可以了。如果看过前两章的介绍的话,应该很简单

谢谢您的鼓励~