反射工具类,如斯优雅

logo
logo

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());

设置反射的方法

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

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);
}

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

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

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

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 也是存在这个问题,我也已经对其修复了。

还有就是 jOORprivate static final 字段先 getset 会报异常 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 的成长。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容