java反射整理

作者: 的一幕 | 来源:发表于2019-08-09 11:00 被阅读19次

    反射可以说是在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

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

    相关文章

      网友评论

        本文标题:java反射整理

        本文链接:https://www.haomeiwen.com/subject/blcfjctx.html