Foreword
反射的作用我在這就不多說了,每次用到反射都是那麼一坨程式碼丟進去,總是讓人覺得很不優雅,如今有了我這個反射工具類,那麼大家就可以一句話優雅地來完成反射的工作,該工具類是站在 jOOR 的肩膀上進行改造,修復了它沒有完成的工作,至於修復了什麼,後面原始碼分析會詳述,至於這個工具類在哪,現已加入至 1.12.0 版本的 AndroidUtilCode,下面來介紹下其功能。
Functions
其 APIs 如下所示:
反射相關 -> ReflectUtils.java -> Test
reflect : 設定要反射的類
newInstance: 例項化反射物件
field : 設定反射的欄位
method : 設定反射的方法
get : 獲取反射想要獲取的
複製程式碼
Use
例項化反射物件
比如,我們例項化一個 String
物件可以這樣做:
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());
複製程式碼
設定反射的方法
比如,我們想要呼叫 String
的 substring
函式可以這樣做:
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
如下所示:
public class TestPrivateStaticFinal {
private static final int I1 = new Integer(1);
private static final Integer I2 = new Integer(1);
}
複製程式碼
我們要設定其 I1
、I2
值為 2,可以如下操作:
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I1", 2);
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I2", 2);
複製程式碼
要獲取其 I1
、I2
值的話,可以如下操作:
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I1").get()
ReflectUtils.reflect(TestPrivateStaticFinal.class).field("I2").get()
複製程式碼
當然,欄位操作也有更高階的操作,比如 Test1.java
測試類如下所示:
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;
}
複製程式碼
我對其進行的單元測試如下所示:
@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
的操作,其涉及的程式碼如下所示:
/**
* 例項化反射物件
*
* @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
進行排序,而是直接返回了第一個與之匹配的。這樣說有點抽象,我舉個例子應該就明白了,比如說有兩個建構函式如下所示:
public class Test {
public Test(Number n) {
}
public Test(Object n) {
}
}
複製程式碼
jOOR 反射呼叫建構函式引數傳入 Long
型別,很可能就會走 Test(Object n)
這個建構函式,而我修改過後就是對多個符合的 Constructor
進行排序,匹配出與之最接近的父類,也就是會走 Test(Number n)
這個建構函式,同理,在後面的 method
中的引數匹配 jOOR 也是存在這個問題,我也已經對其修復了。
還有就是 jOOR 對 private static final
欄位先 get
再 set
會報異常 java.lang.IllegalAccessException
異常,是因為對 private static final
欄位 get
的時候沒有去除 final
屬性,如果在 get
時就把 final
去掉即可解決,那樣在 set
的時候就不會報錯。然而,在 Android 的 SDK 中是沒有 Field.class.getDeclaredField("modifiers")
這個欄位的,所以會報 NoSuchFieldException
異常,這方面我做了容錯處理,相關程式碼如下所示:
/**
* 設定反射的欄位
*
* @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 的成長。