反射工具类,如斯优雅

logo

Foreword

反射的作用我在这就不多说了,每次用到反射都是那么一坨代码丢进去,总是让人觉得很不优雅,如今有了我这个反射工具类,那么大家就可以一句话优雅地来完成反射的工作,该工具类是站在 jOOR 的肩膀上进行改造,修复了它没有完成的工作,至于修复了什么,后面源码分析会详述,至于这个工具类在哪,现已加入至 1.12.0 版本的 AndroidUtilCode,下面来介绍下其功能。

Functions

其 APIs 如下所示:

反射相关 -> ReflectUtils.java -> Test

1
2
3
4
5
reflect    : 设置要反射的类
newInstance: 实例化反射对象
field : 设置反射的字段
method : 设置反射的方法
get : 获取反射想要获取的

Use

实例化反射对象

比如,我们实例化一个 String 对象可以这样做:

1
2
3
4
5
6
7
8
String str1 = ReflectUtils.reflect(String.class).newInstance().get();
// equals: String str1 = new String();

String str2 = ReflectUtils.reflect("java.lang.String").newInstance("abc").get();
// equals: String str2 = new String("abc");

String str3 = ReflectUtils.reflect(String.class).newInstance("abc".getBytes()).get();
// equals: String str3 = new String("abc".getBytes());

设置反射的方法

比如,我们想要调用 Stringsubstring 函数可以这样做:

1
2
3
4
5
String str1 = ReflectUtils.reflect((Object) "1234").method("substring", 2).get();
// equals: String str1 = "1234".substring(2);

String str2 = ReflectUtils.reflect((Object) "1234").method("substring", 0, 2).get();
// equals: String str1 = "1234".substring(0, 2);

设置反射的字段

比如,TestPrivateStaticFinal.java 如下所示:

1
2
3
4
public class TestPrivateStaticFinal {
private static final int I1 = new Integer(1);
private static final Integer I2 = new Integer(1);
}

我们要设置其 I1I2 值为 2,可以如下操作:

1
2
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I1", 2);
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I2", 2);

要获取其 I1I2 值的话,可以如下操作:

1
2
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I1").get()
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I2").get()

当然,字段操作也有更高级的操作,比如 Test1.java 测试类如下所示:

1
2
3
4
5
6
7
8
9
public class Test1 {
public static int S_INT1;
public static Integer S_INT2;
public int I_INT1;
public Integer I_INT2;

public static Test1 S_DATA;
public Test1 I_DATA;
}

我对其进行的单元测试如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void fieldAdvanced() throws Exception {
ReflectUtils.reflect(Test1.class)
.field("S_DATA", ReflectUtils.reflect(Test1.class).newInstance())// 设置 Test1.class 中 S_DATA 字段 为 new Test1()
.field("S_DATA")// 获取到 Test1.class 中 S_DATA 字段
.field("I_DATA", ReflectUtils.reflect(Test1.class).newInstance())// 获取到 Test1.class 中 S_DATA 字段 的 I_DATA 为 new Test1()
.field("I_DATA")// 获取到 Test1.class 中 S_DATA 字段 的 I_DATA 字段
.field("I_INT1", 1)// 设置 Test1.class 中 S_DATA 字段 的 I_DATA 字段的 I_INT1 值为 1
.field("S_INT1", 2);// 设置 Test1.class 中 S_DATA 字段 的 S_INT1 字段的 I_INT1 值为 2
assertEquals(2, Test1.S_INT1);// 静态变量就是最后设置的 2
assertEquals(null, Test1.S_INT2);// 没操作过就是 null
assertEquals(0, Test1.S_DATA.I_INT1);// 没操作过就是 0
assertEquals(null, Test1.S_DATA.I_INT2);// 没操作过就是 0
assertEquals(1, Test1.S_DATA.I_DATA.I_INT1);// 倒数第二步操作设置为 1
assertEquals(null, Test1.S_DATA.I_DATA.I_INT2);// 没操作过就是 null
}

根据如上注释相信大家也可以理解一二了,如果还想了解更多使用方式,可以查看我写的单元测试类 ReflectUtilsTest,其使用方式就介绍到这里,下面介绍其实现方式。

Achieve

实现的话是站在 jOOR 的肩膀上进行改造,其内部封装了一个 private final Object object; 变量,每次进行反射操作时都会重新实例化一个变量并把结果赋予该变量,最终 get() 就是获取其值,比如我们来看一下 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* 实例化反射对象
*
* @param args 实例化需要的参数
* @return {@link ReflectUtils}
*/
public ReflectUtils newInstance(Object... args) {
Class<?>[] types = getArgsType(args);
try {
Constructor<?> constructor = type().getDeclaredConstructor(types);
return newInstance(constructor, args);
} catch (NoSuchMethodException e) {
List<Constructor<?>> list = new ArrayList<>();
for (Constructor<?> constructor : type().getDeclaredConstructors()) {
if (match(constructor.getParameterTypes(), types)) {
list.add(constructor);
}
}
if (list.isEmpty()) {
throw new ReflectException(e);
} else {
sortConstructors(list);
return newInstance(list.get(0), args);
}
}
}

private Class<?>[] getArgsType(final Object... args) {
if (args == null) return new Class[0];
Class<?>[] result = new Class[args.length];
for (int i = 0; i < args.length; i++) {
Object value = args[i];
result[i] = value == null ? NULL.class : value.getClass();
}
return result;
}

private void sortConstructors(List<Constructor<?>> list) {
Collections.sort(list, new Comparator<Constructor<?>>() {
@Override
public int compare(Constructor<?> o1, Constructor<?> o2) {
Class<?>[] types1 = o1.getParameterTypes();
Class<?>[] types2 = o2.getParameterTypes();
int len = types1.length;
for (int i = 0; i < len; i++) {
if (!types1[i].equals(types2[i])) {
if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) {
return 1;
} else {
return -1;
}
}
}
return 0;
}
});
}

private ReflectUtils newInstance(final Constructor<?> constructor, final Object... args) {
try {
return new ReflectUtils(
constructor.getDeclaringClass(),
accessible(constructor).newInstance(args)
);
} catch (Exception e) {
throw new ReflectException(e);
}
}

private final Class<?> type;

private final Object object;

private ReflectUtils(final Class<?> type, Object object) {
this.type = type;
this.object = object;
}

jOOR 所没有做到的就是没有对多个符合的 Constructor 进行排序,而是直接返回了第一个与之匹配的。这样说有点抽象,我举个例子应该就明白了,比如说有两个构造函数如下所示:

1
2
3
4
5
6
7
8
public class Test {

public Test(Number n) {
}

public Test(Object n) {
}
}

jOOR 反射调用构造函数参数传入 Long 类型,很可能就会走 Test(Object n) 这个构造函数,而我修改过后就是对多个符合的 Constructor 进行排序,匹配出与之最接近的父类,也就是会走 Test(Number n) 这个构造函数,同理,在后面的 method 中的参数匹配 jOOR 也是存在这个问题,我也已经对其修复了。

还有就是 jOORprivate static final 字段先 getset 会报异常 java.lang.IllegalAccessException 异常,是因为对 private static final 字段 get 的时候没有去除 final 属性,如果在 get 时就把 final 去掉即可解决,那样在 set 的时候就不会报错。然而,在 Android 的 SDK 中是没有 Field.class.getDeclaredField("modifiers") 这个字段的,所以会报 NoSuchFieldException 异常,这方面我做了容错处理,相关代码如下所示:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 设置反射的字段
*
* @param name 字段名
* @return {@link ReflectUtils}
*/
public ReflectUtils field(final String name) {
try {
Field field = getField(name);
return new ReflectUtils(field.getType(), field.get(object));
} catch (IllegalAccessException e) {
throw new ReflectException(e);
}
}

/**
* 设置反射的字段
*
* @param name 字段名
* @param value 字段值
* @return {@link ReflectUtils}
*/
public ReflectUtils field(String name, Object value) {
try {
Field field = getField(name);
field.set(object, unwrap(value));
return this;
} catch (Exception e) {
throw new ReflectException(e);
}
}

private Field getField(String name) throws IllegalAccessException {
Field field = getAccessibleField(name);
if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
try {
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
} catch (NoSuchFieldException ignore) {
// runs in android will happen
}
}
return field;
}

private Field getAccessibleField(String name) {
Class<?> type = type();
try {
return accessible(type.getField(name));
} catch (NoSuchFieldException e) {
do {
try {
return accessible(type.getDeclaredField(name));
} catch (NoSuchFieldException ignore) {
}
type = type.getSuperclass();
} while (type != null);
throw new ReflectException(e);
}
}

private Object unwrap(Object object) {
if (object instanceof ReflectUtils) {
return ((ReflectUtils) object).get();
}
return object;
}

所以该工具类既完美支持 Java,也完美支持 Android。

Conclusion

好了,这次反射工具类就介绍到这了,是不是觉得如斯优雅,如果觉得好的话以后遇到反射的问题,那就快用我这个工具类吧,这么好的东西藏着不用真的是可惜了哦。

关于安卓核心常用工具类我已经差不多都封装了,今后应该也不会在核心的里面新增了,除非确实很需要我才会再新增某个工具类,其余不常用的我都会放在 subutil 中,感谢大家一直陪伴着 AndroidUtilCode 的成长。

打个小广告

欢迎加入我的小专栏「基你太美」一起学习。