每周一博

反射机制

2018-12-10  本文已影响14人  健身营养爱好者

前言

HI,欢迎来到裴智飞的《每周一博》。今天是十二月第二周,我给大家介绍一下反射的知识,为什么要介绍这些呢,因为插件化技术需要它作为基础。

一. 反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java的反射机制。

加载一个类的时候,jvm会去寻找Class文件并载入到内存中,在运行期间一个Class对象在内存里只有一个,反射就是在运行时从内存中解析Class对象,把属性和方法映射成一个个的Java对象,原理如图。

反射就好比察看一个人的内脏器官,然后判断出这个人的健康状况,反射是Java作为一种动态语言的关键性质,利用它可以实现动态编译。

二. 获取Class对象

类运行时的类型信息就是用Class对象表示的,它包含了与类有关的信息。每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。

Class对象对应着java.lang.Class类,它没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:

所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类,使用new创建类对象的时候也会被当作对类的静态成员的引用。因此Java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。

在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载,如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。

要想反射一个类,必须先要获取到该类的Class对象,可以通过三种方法获取Class对象。

  1. 通过Object类中的 getClass() 方法,这是有了对象时候调用的,但是有了对象其实没必要反射了,除非调用私有属性;
Car car = new Car();
Class<?> s = car.getClass();
  1. 每个类都有一个静态的属性class,可以直接通过该属性获得Class对象,这种情况是需要导入包的,依赖性太强;
Class<?> s = Car.class;
  1. 通过Class.forName()方法完成,必须要指定类的全名含包名,适用于不知道类的情况,当然它会抛ClassNotFoundException异常,一般会把字符串写入配置文件中来实现模块解耦。
Class<?> c = Class.forName("com.refelct.Car");

还有个好处是通过Class.forName()会初始化静态块,而前两者不会。我们知道当一个类的静态块被调用的时候会进行首次加载,但如果一个字段被static final修饰,那么在调用这个字段的时候是不会对类进行初始化的。因为被static和final修饰的字段,在编译期就把结果放入了常量池中了,但是如果只是将一个域设置为static 或final的,还是会对类进行初始化的。

Class的方法有很多,和反射的方法很类似,这里列举一些常用的。

三. 反射的使用

  1. 反射的API主要涉及这么几个类:Constructor描述构造函数,Field描述变量,Method描述方法。我们一般用getDeclaredXXX来获取对应的类型,getDeclaredXXX和getXXX的区别是前者可以获取到私有类型,下面一个例子打印出了String类的信息。
package com.reflect;
 
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
 
public class Demo {
 
    public static void main(String[] args) throws ClassNotFoundException {
    
        System.out.println("printFieldInfo start===========");
        printFieldInfo("hello");
        System.out.println("printFieldInfo end=============");
        
        System.out.println("printMethodInfo start===========");
        printMethodInfo("hello");
        System.out.println("printMethodInfo end===========");
            
        System.out.println("printConstructorInfo start===========");
        printConstructorInfo("hello");
        System.out.println("printConstructorInfo end===========");
    }
    
    public static void printFieldInfo(Object o){
        Class<?> clazz = o.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Field field = declaredFields[i];
            System.out.print((i+1)+" ");
            System.out.print(Modifier.toString(field.getModifiers())+" ");
            System.out.print(field.getType().getSimpleName()+" ");
            System.out.print(field.getName());
            System.out.println(";");
        }
    }
    
    public static void printMethodInfo(Object o){
        Class<?> clazz = o.getClass();
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (int i = 0; i < declaredMethods.length; i++) {
            Method method = declaredMethods[i];
            System.out.print((i+1)+" ");
            System.out.print(Modifier.toString(method.getModifiers())+" ");
            System.out.print(method.getReturnType().getSimpleName());
            System.out.print("(");
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                Class<?> parameterType =parameterTypes[j];
                if(j==parameterTypes.length-1){
                    System.out.print(parameterType.getSimpleName()+" arg"+j);
                }else{
                    System.out.print(parameterType.getSimpleName()+" arg"+j+",");
                }
            }
            System.out.println(");");
        }
    }
    
    
    public static void printConstructorInfo(Object o){
        Class<?> clazz = o.getClass();
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (int i = 0; i < declaredConstructors.length; i++) {
            Constructor<?> constructor = declaredConstructors[i];
            System.out.print((i+1)+" ");
            System.out.print(Modifier.toString(constructor.getModifiers())+" ");
            System.out.print(clazz.getSimpleName());
            System.out.print("(");
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                Class<?> parameterType =parameterTypes[j];
                if(j==parameterTypes.length-1){
                    System.out.print(parameterType.getSimpleName()+" arg"+j);
                }else{
                    System.out.print(parameterType.getSimpleName()+" arg"+j+",");
                }
            }
            System.out.println(");");
        }
    }
 
}

打印结果如下;

printFieldInfo start===========
1 private final char[] value;
2 private int hash;
3 private static final long serialVersionUID;
4 private static final ObjectStreamField[] serialPersistentFields;
5 public static final Comparator CASE_INSENSITIVE_ORDER;
printFieldInfo end=============
printMethodInfo start===========
1 public boolean(Object arg0);
2 public String();
……
76 public String(Locale arg0);
77 public String();
printMethodInfo end===========
printConstructorInfo start===========
1 public String(byte[] arg0,int arg1,int arg2);
2 public String(byte[] arg0,Charset arg1);
……
15 public String(byte[] arg0,int arg1);
16 public String(byte[] arg0,int arg1,int arg2,int arg3);
printConstructorInfo end===========
  1. 实际操练,编写测试类,定义了有参和无参的构造函数,普通变量,静态变量,普通方法,静态方法。
public class BeReflected {
    // 私有普通变量
    private String field1 = "I am field1";
    // 私有静态变量
    private static String staticField = "I am staticField";
    // 无参构造方法
    public BeReflected() {
    }
    // 有参构造方法
    public BeReflected(String s) {
        field1 = s;
    }
    // 普通无参方法
    private void method1() {
        System.out.println("I am method1");
    }
    // 普通带1个参数方法
    private void method2(String param) {
        System.out.println("I am method1--param = " + param);
    }
    // 普通带多个参数方法
    private void method3(String param, String param2, int param3) {
        System.out.println("param = " + param + " param2 = " + param2 + " param3 = " + param3);
    }
    // 静态无参方法
    public static void staticMethod() {
        System.out.println("I am staticMethod");
    }
    // 静态带参数方法
    public static void staticMethod(String s) {
        System.out.println("I am staticMethod:s:" + s);
    }
}
  1. 反射创建对象:通过Class的newInstance对象就可以创建一个对象,所以new只是创建对象的一种方式。当然通过newInstance要求改类需要有一个空的无参构造方法。
Class<?> c = Class.forName("com.refelct.BeReflected");
Object obj = c.newInstance();
  1. 反射调用构造函数:如果想调用有参数的构造函数,就要用到Constructor这个类了,通过Class的getDeclaredConstructor方法可以获得Constructor对象,传入的参数是方法参数类型,比如int要传入int.class,字符串传String.class,字符串数组传String[].class;
Class<?> c = Class.forName("com.refelct.BeReflected");
Constructor ss = c.getDeclaredConstructor(String.class);
Object tt = ss.newInstance("测试构造函数");
  1. 反射获取静态变量:调用属性就要用到Field这个类了,通过getDeclaredField方法获取到属性后需要设置setAccessible(true)。由于静态变量是属于类的,所以不需要类的实例,直接调用Field类的get(null)即可获得;
Class<?> c = Class.forName("com.refelct.BeReflected");
Field field = c.getDeclaredField("staticField");
      if (field != null) {
            field.setAccessible(true);
            Object o = field.get(null);
            System.out.println("o:" + o);
      } 
  1. 反射获取私有普通变量:由于普通变量是属于对象的,所以需要先获得类的实例,然后再调用Field类的get(obj);
Class<?> c = Class.forName("com.refelct.BeReflected");
Object obj = c.newInstance();
Field field = c.getDeclaredField("field1");
      if (field != null) {
            field.setAccessible(true);
            Object o = field.get(obj);
            System.out.println("o:" + o);
      } 

所以静态和非静态的区别在于是否需要传入对象,当然我试了静态方法和属性传入obj也可以获取到。

  1. 反射设置私有变量:通过Field类的set(obj,value)可以修改私有变量。
Class<?> c = Class.forName("com.refelct.BeReflected");
Object obj = c.newInstance();
 Field field = c.getDeclaredField("field1");
            if (field != null) {
                field.setAccessible(true);
                field.set(obj, "测试变量");
                System.out.println("o:" + field.get(obj));
            } 
  1. 反射调用无参方法:方法主要是用到Method类,先通过getDeclaredMethod()获取方法,然后setAccessible(true),接着执行invoke函数,同理静态方法不需要对象。
Class<?> c = Class.forName("com.refelct.BeReflected");
// 调用静态无参方法
Method method = c.getDeclaredMethod("method1");
            if (method != null) {
                method.setAccessible(true);
                method.invoke(null);
            }

// 调用普通无参方法
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("method1");
            if (method != null) {
                method.setAccessible(true);
                method.invoke(obj);
            }
  1. 反射调用有参方法:有参是需要传入参数类型Class<?>... parameterTypes和参数值Object... args的。
Class<?> c = Class.forName("com.refelct.BeReflected");
// 调用普通有1个参数的方法
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("method2",String.class);
            if (method != null) {
                method.setAccessible(true);
                method.invoke(obj,"我是测试值");
            }

// 调用普通有多个参数的方法
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("method3",
      new Class<?>[]{String.class, String.class, int.class});
            if (method != null) {
                method.setAccessible(true);
                method.invoke(obj, new Object[]{"1", "2", 3});
            }

四. 反射的其他知识

  1. final变量可以反射吗?
    如果是直接声明的,是无法反射的,因为编译期间final类型的数据自动被优化了,即所有用到该变量的地方都被替换成了常量。所以 get方法在编译后自动优化成了return "gps",而不是 return GPS_PROVIDER。
private static final String GPS_PROVIDER = "gps";

但如果不是直接定义的就可以反射

private static final String GPS_PROVIDER ;
public LocationManager(){
           GPS_PROVIDER = "gps";
}
  1. 只能反射自己jvm所包含的class,不能反射别的进程里的类,比如想反射微信里的某个字段,那是不可能的。

五. 反射的问题

  1. 反射的效率问题:反射比直接调用实例要慢,getMethod和getDeclaredField方法会比invoke和set方法耗时,详细介绍可以参考这篇文章。这里我们做个测试,反射调用静态方法正常调用静态方法各100000遍。
    private static void testTime() throws Exception {
        {
            long t1 = System.currentTimeMillis();
            Class<?> c = Class.forName("com.refelct.BeReflected");
            Object obj = c.newInstance();
            Method m = c.getDeclaredMethod("method1");
            m.setAccessible(true);
            for (int i = 0; i < 100000; i++) {
                m.invoke(obj);
            }
            long t2 = System.currentTimeMillis() - t1;
            System.out.println("反射消耗:" + t2);
        }
        {
            long t1 = System.currentTimeMillis();
            BeReflected beReflected = new BeReflected();
            for (int i = 0; i < 100000; i++) {
                beReflected.method1();
            }
            long t2 = System.currentTimeMillis() - t1;
            System.out.println("正常消耗:" + t2);
        }
    }


结果打印:
正常消耗:8ms
反射消耗:65ms

可以看到但是调用invoke就很耗时了,还没有把getMethod和Class.forName加入循环,那么有什么可以提高反射效率的方法吗?这里我想到这么几点;

            IRe obj = (IRe) c.newInstance();
            // BeReflected obj = (BeReflected) c.newInstance();
            for (int i = 0; i < 100000; i++) {
                obj.method1();
            }
结果打印:
正常消耗:7ms
反射消耗:5ms
  1. 反射的安全问题:因为可以随意修改类的所有状态和行为,包括private方法和实例,所以如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;

  2. 兼容性问题:反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常,最常见的就是针对安卓版本的适配;

  3. 其他问题:有用到反射的类不能被混淆,静态编译时没有用到的类和资源不能被删除,否则反射找不到;

  4. 反射的特点:
    反射自由度高,不受类的访问权限限制;
    反射存在性能问题,但使用不频繁时对程序性能影响并不大;
    反射是改不了方法,拦截方法需要采用动态代理;

六. 反射的用途

为什么需要反射呢,这用途说来就很多了。

  1. 构建框架:一般构建框架的时候会用到反射,比如创建Activity的时候就用到了newInstance方法,Java当中的很多框架都采用反射。
  2. 构建设计模式:比如一个采用反射来创建对象的工厂模式。
public class Factory {
    public static <T extends Product> T getProduct(String className){
        Class<?> cls = Class.forName(className);
        Product product = (Product) cls.newInstance();
        return (T) product;
    }
}
  1. 按需加载类,节省编译和初始化APK的时间。动态加载第三方jar包,解决安卓开发中方法数不能超过65536个的问题;
  2. 通过反射运行配置文件,实现解耦,其实也是设计框架的思想;
  3. 跳过泛型检查:如果我们往List里添加元素,必须符合类型检查,如果不符合就编译不过,泛型是在编译期检查的,通过反射就可以在运行时跳过这个限制。
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
// 正常情况下添加一个100是无法通过编译的
// strList.add(100);

//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); 
Method m = listClass.getMethod("add", Object.class);
m.invoke(strList, 100);

// 输出的时候注意这里用了Object,而不是String
for(Object obj : strList){
    System.out.println(obj);
}
  1. 反射执行main方法
    Class clazz = Class.forName("fanshe.main.Student");
    Method methodMain = clazz.getMethod("main", String[].class);
    // 方式一
    methodMain.invoke(null, (Object)new String[]{"a","b","c"});
    // 方式二
    // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});
  1. 写一个简单的反射工具类ReflectUtils,但实际做框架时还是要转成接口的,还要考虑泛型,不能一味的使用Object。
public class ReflectUtils {

    // 反射一个对象的无参方法,静态方法obj可以传null
    // ReflectUtils.invoke(c, "staticMethod", obj)
    public static Object invoke(Class<?> c, String methodName, Object obj) {
        try {
            Method method = c.getDeclaredMethod(methodName);
            if (method != null) {
                method.setAccessible(true);
                return method.invoke(obj);
            } else {
                p("该方法不存在");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // 反射一个对象的有一个参数方法,静态方法obj可以传null
    // ReflectUtils.invoke(c, "staticMethod", obj, String.class, "test");
    public static void invoke(Class<?> c, String methodName, Object obj, Class<?> target, Object params) {
        try {
            Method method = c.getDeclaredMethod(methodName, target);
            if (method != null) {
                method.setAccessible(true);
                method.invoke(obj, params);
            } else {
                p("该方法不存在");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 反射一个对象的有多个参数方法,静态方法obj可以传null
    // ReflectUtils.invoke(c, "method4", obj, new Class<?>[]{String.class, String.class},
    // new String[]{"1", "2"});
    // ReflectUtils.invoke(c, "method3", obj, new Class<?>[]{String.class, String.class, int.class},
    // new Object[]{"1", "2", 3});
    public static void invoke(Class<?> c, String methodName, Object obj, Class<?>[] target, Object[] params) {
        try {
            Method method = c.getDeclaredMethod(methodName, target);
            if (method != null) {
                method.setAccessible(true);
                method.invoke(obj, params);
            } else {
                p("该方法不存在");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取一个对象的值,静态变量obj可以传null
    // ReflectUtils.getValue(c, "pubfield1", obj);
    public static Object getValue(Class<?> c, String fieldName, Object obj) {
        try {
            Field field = c.getDeclaredField(fieldName);
            if (field != null) {
                field.setAccessible(true);
                return field.get(obj);
            } else {
                p("该变量不存在");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    // 修改一个对象的值,静态变量obj可以传null
    // ReflectUtils.setValue(c, "staticField", obj, "测试变量")
    public static Object setValue(Class<?> c, String fieldName, Object obj, Object value) {
        try {
            Field field = c.getDeclaredField(fieldName);
            if (field != null) {
                field.setAccessible(true);
                field.set(obj, value);
                return field.get(obj);
            } else {
                p("该变量不存在");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // 传入被代理对象的classloader,实现的接口,还有DynamicProxyHandler的对象即可
    public static Object newProxyInstance(Object object, InvocationHandler invocationHandler) {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),
                object.getClass().getInterfaces(), invocationHandler);
    }


    public static void p(String s) {
        System.out.println("" + s);
        // Log.e("gzq",""+s);
    }
}

七. 总结

本文介绍了反射的一些基本知识,也是为下篇动态代理做一个铺垫,感谢大家的阅读,我们下周再见。

上一篇下一篇

猜你喜欢

热点阅读