java反射整理

反射可以说是在android源码中的影子无处不在,为了更好地理解android源码,所以该篇整理出反射中经常看到的用法,反射的代码是在运行时期运行,因此在编译器不会提示代码的错误性,只有在运行期才能检索代码,下面通过几个简单的例子来看看如何使用:

调用类的构造方法

写了一个简单的测试类:

package com.single.layoutinflaterdemo;

public class Reflection {
    private String params1;
    private int params2;
    private long params3;

   public Reflection() {
        System.out.println("调用了Reflection类的无参数构造器");
    }

    public Reflection(String params1) {
        this.params1 = params1;
        System.out.println("调用了Reflection类的params1参数构造器====>params1:" + params1);
    }

    public Reflection(int params2) {
        this.params2 = params2;
        System.out.println("调用了Reflection类的params2参数构造器====>params2:" + params2);
    }

    public Reflection(long params3) {
        this.params3 = params3;
        System.out.println("调用了Reflection类的params3参数构造器====>params3:" + params3);
    }
}

事例代码如下:

public static void main(String[] args) {
    try {
        Class clz = Class.forName("com.single.layoutinflaterdemo.Reflection");
        Constructor appleConstructor1 = clz.getConstructor();
        Object appleObj1 = appleConstructor1.newInstance();

        Constructor appleConstructor2 = clz.getConstructor(String.class);
        Object appleObj2 = appleConstructor2.newInstance("123");

        Constructor appleConstructor3 = clz.getConstructor(int.class);
        Object appleObj3 = appleConstructor3.newInstance(11);

        Constructor appleConstructor4 = clz.getConstructor(long.class);
        Object appleObj4 = appleConstructor4.newInstance(11l);

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}
image.png

看到了没,所有类型的构造方法都能执行,那咱们可以看下如果没有定义的构造方法,而在程序中调用了会出现什么呢:

Class clz = Class.forName("com.single.layoutinflaterdemo.Reflection");
Constructor appleConstructor1 = clz.getConstructor(float.class);
Object appleObj1 = appleConstructor1.newInstance(12.23);

image.png

因为在Reflection类中,没有定义float类型的构造方法,因此会抛了一个NoSuchMethodException异常。下面再看一种构造器调用的方式,这个得追索到上篇LayoutInflater背后隐藏的一些东西说事,直接到调用view的构造器的代码出:
image.png

这里是利用:Class<?> clz=context.getClassLoader().loadClass(name)
这里我们增加一个类:
image.png

啥也没有,只是为了说明asSubclass的作用,修改如下:
image.png

image.png

结果说是强转失败的问题,下面咱们改下Reflection类:
image.png

结果不会报上面的ClassCastException,能正常执行代码,因此可以看出asSubclass的作用,限制了类的类型,减小了类的范围
再来看个例子,这里是初始化CoordinatorLayout.Behavior
image.png

此处是通过三个参数获取到Class<Behavior>,默认只传入name的forName也是调的该方法,第二个参数表示是不是初始化该类,第三个参数表示需要传入的classLoaderandroid中经常用到的activity也是通过该方式创建成功的,具体可以看activity启动流程。

调用类的方法

这里简单在类里面定义了几个方法:

public int handleMethod(int a, String b) throws NumberFormatException {
    System.out.println("调用了两个参数的handleMethod方法");
    int intB = Integer.parseInt(b);
    return a + intB;
}

public void handleMethod() {
    System.out.println("调用了无参的handleMethod方法");
}

public int handleMethod(int a) {
    System.out.println("调用了一个参数的handleMethod方法");
    return a;
}

public void handleMethod(String params1) {
    this.params1 = params1;
    System.out.println("调用了一个参数的handleMethod方法====>params1:" + params1);
}

private void privateMethod() {
    System.out.println("调用了私有的privateMethod方法");
}

protected void protectedMethod() {
    System.out.println("调用了私有的protectedMethod方法");
}

调用无参数的方法

Class clz = Class.forName("com.single.layoutinflaterdemo.Reflection");
clz.asSubclass(Common.class);
Constructor appleConstructor1 = clz.getConstructor(int.class);
Object target = appleConstructor1.newInstance(123);
//指定方法名
Method handleMethod = clz.getMethod("handleMethod");
//通过invoke方法执行handleMethod
handleMethod.invoke(target);
image.png

是不是so easy呢,没错,就是指定方法名,然后通过method.invoke方法执行要反射调用的方法。

调用有参的方法

Class clz = Class.forName("com.single.layoutinflaterdemo.Reflection");
clz.asSubclass(Common.class);
Constructor appleConstructor1 = clz.getConstructor(int.class);
Object target = appleConstructor1.newInstance(123);
//指定要调用方法的参数类型
Class<?>[] params = new Class[]{int.class, String.class};
Method handleMethod = clz.getMethod("handleMethod", params);
//args传入我们的参数值
Object invoke = handleMethod.invoke(target, 10, "20");
System.out.println("返回值:" + invoke);
image.png

当需要调用指定带参数的方法时候,需要在Class.getMethod方法中指定反射调用的方法传入的参数类型,然后在invoke的时候传入参数值就行。
下面看一个特例,如果传入的值跟参数类型不一致呢,会出现什么情况:


image.png

此处将参数值改成普通的字符串直接抛出异常了,即使咋们在定义方法的时候抛出该异常信息,但在反射期间还是不能自己处理异常,因此咋们只能在定义方法内部去处理异常:


image.png

调用private或protected方法

Class clz = Class.forName("com.single.layoutinflaterdemo.Reflection");
clz.asSubclass(Common.class);
Constructor appleConstructor1 = clz.getConstructor(int.class);
appleConstructor1.setAccessible(true);
Object target = appleConstructor1.newInstance(123);
//指定要调用方法的参数类型
Class<?>[] params = new Class[]{int.class, String.class};
//通过getDeclaredMethod方法获取
Method handleMethod = clz.getDeclaredMethod("privateMethod");
//并且设置可访问的权限
handleMethod.setAccessible(true);
//args传入我们的参数值
Object invoke = handleMethod.invoke(target);

image.png

注意两点:通过getDeclaredMethod方法获取要反射的方法,设置method.setAccessible(true)访问权限,其实在调用私有的构造方法的时候也可以按照此方式调用,getDeclaredConstructor和setAccessible也是一起使用

访问类的变量

访问单个变量

//调用指定的参数名
Field params1 = clz.getDeclaredField("params1");
params1.setAccessible(true);
//获取参数的值
Object name = params1.get(target);

由于一般类的变量都是private,所以只列举private情况,也是通过getDeclaredField(参数名)、field.setAccessible(true)一起完成,最后通过field.get(target)来获取参数值
访问所有的变量

//获取所有的参数
Field[] allParams = clz.getDeclaredFields();
for (int i = 0; i < allParams.length; i++) {
    Field param = allParams[i];
    param.setAccessible(true);
    String name = param.getName();
    Object value = param.get(target);
    System.out.println("name:" + name + ";value:" + value);
}

image.png

通过getDeclaredFields方法获取到所有的filed[]数组,然后跟普通的单个变量是一样的获取name和value

继承关系,调用父类的方法或变量

//写了个父类,用来测试的
public class Common {
    private String baseParam1 = "123";
    protected int baseParam2 = 11;
    public int baseParam3 = 123;

    public void baseHandleMethod() {
        System.out.println("调用了父类的baseHandleMethod方法");
    }

    protected void baseHandleMethod(String baseParam1) {
        this.baseParam1 = baseParam1;
        System.out.println("调用了父类protected类型的baseHandleMethod方法");
    }
}

调用父类的public类型的方法

//调用父类的public类型的方法
Method baseHandleMethod = clz.getMethod("baseHandleMethod");
baseHandleMethod.invoke(target);

调用public类型是一切没有问题的,下面尝试下调用父类的protected或者private方法:

//调用父类的protect或private方法
//错误的写法
Method protectedBaseHandleMethod = clz.getDeclaredMethod("baseHandleMethod", String.class);
protectedBaseHandleMethod.setAccessible(true);
protectedBaseHandleMethod.invoke(target, "124");

可以看下运行结果:


image.png

直接报错,说找不到该方法,下面换种方式写:

//调用父类的protect或private方法
//正确的写法,通过getSuperclass获取到父类的class对象
Method protectedBaseHandleMethod = clz.getSuperclass().getDeclaredMethod("baseHandleMethod", String.class);
protectedBaseHandleMethod.setAccessible(true);
protectedBaseHandleMethod.invoke(target, "124");

可以看到如果想获取父类的protected或private方法必须先通过getSuperclass方法获取到父类的class对象。

获取父类的变量跟方法是一样的,大家自己尝试就行,这里就不给大家演示了。

是不是感觉反射拿到了一个类的全类名,就可以为所欲为呢,确实很强大,绕过了类的编译期,在运行的时候直接运行你的class字节码文件。熟悉反射,对android相关源码还是有很大帮助,所以大家还是要亲自写demo才能体会它的强大。

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