反射

作者: couriravant | 来源:发表于2019-12-11 17:41 被阅读0次

    一、什么是反射

    反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活应用的主要工具。

    二、作用

    1. 调用一些私有方法,实现黑科技。比如双卡短信发送、设置状态栏颜色、自动挂电话等。

    2. 实现序列化与反序列化,比如PO的ORM,Json解析等。

    3. 实现跨平台兼容,比如JDK中的SocketImpl的实现

    4. 通过xml或注解,实现依赖注入(DI),注解处理,动态代理,单元测试等功能。比如Retrofit、Spring或者Dagger

    三、基本使用

    代码块

    package com.example.myapplication.reflect;
    
    import java.lang.reflect.AccessibleObject;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    
    public class Apple {
    
        private String appleName;
        private int price;
        private static final String name = new String("巫婆的苹果");
    
        private Apple() {}
    
        private Apple (String appleName, int price) {
            this.appleName = appleName;
            this.price = price;
        }
    
        private void setPrice(int price) {
            this.price = price;
        }
    
        private int getPrice () {
            return price;
        }
    
        private String getName() {
            return name;
        }
    
        public static void main(String[] args) {
            Apple apple = new Apple();
    
            try {
                //三种获取Class的方法,反射主要用第一种
                Class clz = Class.forName("com.example.myapplication.reflect.Apple");
                Class clz1 = apple.getClass();
                Class clz2 = Apple.class;
    
                //获取类实例的两种方法
                Apple apple1 = (Apple) clz.newInstance();
                Apple apple2 = (Apple)clz1.getDeclaredConstructor(String.class, int.class).newInstance("白雪公主", 1000);
                System.out.println("apple name:"+apple2.appleName);
    
                //获取方法对象
                Method method = clz.getDeclaredMethod("setPrice", int.class);
                method.setAccessible(true);
                method.invoke(apple1, 5);
    
                Method getMethod = clz.getDeclaredMethod("getPrice");
                getMethod.setAccessible(true);
                System.out.println("反射方法对象获取price"+getMethod.invoke(apple1));
    
                //遍历方法
                Method[] methods = clz.getDeclaredMethods();
                AccessibleObject.setAccessible(methods,true);
    
                for(Method method1 :methods) {
    //                method1.setAccessible(true);
                    System.out.println("类中的方法有:"+method1.getName());
                }
    
                //获取变量
                Field field = clz.getDeclaredField("price");
                field.setAccessible(true);
                int newPrice = field.getInt(apple1);
                System.out.println("直接反射变量获取到的price"+newPrice);
    
                //遍历变量
                for(Field field1 : clz.getDeclaredFields()) {
                    field1.setAccessible(true);
                    System.out.println("获取到的变量名:"+field1.getName());
                }
    
                //获取final变量 ,被内联的没用,这样似乎也没什么用,上面的new String("巫婆的苹果") 是为了防止内联
                Field nameField = apple1.getClass().getDeclaredField("name");
                Field modifiersField = Field.class.getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);
                nameField.setAccessible(true); //这个同样不能少,除非上面把 private 也拿掉了,可能还得 public
                nameField.set(null, "巫婆的第二个苹果");
                System.out.println("修改final变量后 "+apple1.getName());
    
            } catch (Exception e) {
                System.out.println("exception:"+e.getMessage());
            }
        }
    }
    

    输出:

    apple name:白雪公主
    反射方法对象获取price5
    类中的方法有:main
    类中的方法有:getName
    类中的方法有:getPrice
    类中的方法有:setPrice
    直接反射变量获取到的price5
    获取到的变量名:appleName
    获取到的变量名:price
    获取到的变量名:name
    修改final变量后 巫婆的第二个苹果
    

    四、怎么理解反射慢

    在Stackoverflow上认为反射比较慢的程序员主要有如下看法

    1. 验证等防御代码过于繁琐,这一步本来在link阶段,现在却在计算时进行验证

    2. 产生很多临时对象,造成GC与计算时间消耗

    3. 由于缺少上下文,丢失了很多运行时的优化,比如JIT(它可以看作JVM的重要评测标准之一)

    当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。

    优化反射:

    为什么反射慢:
    1)getMethod和getDeclaredField方法会比invoke和set方法耗时;

    2)随着测试数量级越大,性能差异的比例越趋于稳定;

    如何优化:

    1)善用API
    比如,尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法。
    2)缓存大法好
    比如,需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多。还有将反射得到的method/field/constructor对象做缓存。
    3) 尽量使用高版本jdk
    4)ReflectASM通过字节码生成的方式加快反射速度

    refer:
    http://www.throwable.club/2018/12/16/java-reflection-optimal-performance/#%E6%96%B9%E6%B3%95%E4%B8%89%EF%BC%9A%E5%8F%8D%E5%B0%84%E6%93%8D%E4%BD%9C%E8%BD%AC%E5%8F%98%E4%B8%BA%E7%9B%B4%E6%8E%A5%E8%B0%83%E7%94%A8

    http://www.importnew.com/21211.html
    https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

    相关文章

      网友评论

          本文标题:反射

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