反射

作者: Doctor_Xu | 来源:发表于2020-11-28 22:13 被阅读0次

    反射简介

    反射是指程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法。
    使用反射,对于任意一个对象,都能调用它的任意一个属性和方法。因为类的信息保存在Class对象中,而这个Class对象是程序运行时由ClassLoader动态加载到内存中的。当ClassLoader加载了类之后,此类的Class信息就会保存到Java虚拟机内存模型中的方法区中。

    反射所涉及到的类

    • java.long.Class
    • java.lang.reflect.Field
    • java.lang.reflect.Method
    • java.lang.reflect.Modifier
    • java.lang.reflect.Constructor

    实现反射的三种方法

    • Class clazz = Class.forName("com.doctor.reflect.ReflectTest");
      Class.forName还有另外一个重载函数,forName(String name, boolean initialize, ClassLoader loader)用于标识是否初始化类和指定ClassLoader。
      Class.forName()会执行类的初始化,此初始化为加载、验证、准备、解析、初始化中的初始化,即在方法区中分配内存空间并执行static变量的初始化,其非static的全局变量不会在此时进行初始化,此初始化的意义和new一个对象对其初始化的意义不同。
    • Class clazz = ReflectTest.class();
      此操作不会对类进行初始化
    • Class clazz = new ReflectTest.getClass();
      此函数返回的类对象为运行时真正所指的对象,它所属的类型为Class对象

    使用反射

    1. 创建对象
    Class clazz = Class.forName("com.doctor.reflect.ReflectTest"); 
    Object object = clazz.newInstance();
    

    使用newInstance()是创建类的一个实例,使用newInstance()有一个限制,即只能使用无参构造函数。

    1. 构造函数调用
    Class clazz = Class.forName("com.doctor.reflect.ReflectTest"); 
    Constructor<?> constructor = clazz.getConstructor(int.class, String.class);
    Object object = constructor.newInstance(18, "Doctor");
    

    getConstructor函数会返回一个Constructor对象,它表示了反射的类对象中指定的公共构造方法

    1. 获取类中的属性
    Class clazz = Class.forName("com.doctor.reflect.ReflectTest"); 
    Field field = clazz.getDeclaredField("fieldName");
    
    1. 修改类中属性和方法的访问权限
    Class clazz = Class.forName("com.doctor.reflect.ReflectTest"); 
    Field field = clazz.getDeclaredField("fieldName");
    field.setAccessible(true);
    

    使用Field类的setAccessible方法访问限制,用它可以取消private访问限制或protected访问限制
    field.setAccessible(true);设置这个属性可以被访问,假设在原始类中,field的修饰符为private,则其他类不能访问此属性,
    调用setAccessible(true)并不是把此属性的访修饰符改为public。
    在Java虚拟机的安全管理器中会使用checkPermission()方法来检查访问权限,而setAccessible(true)并不是把访问权限修改为public,而是取消Java的访问权限检查,因此对于public修饰的属性和方法,其默认access属性也为false。

    1. 修改属性的值
    Class clazz = Class.forName("com.doctor.reflect.ReflectTest"); 
    Object object = clazz.newInstance();
    Field field = clazz.getDeclaredField("fieldName");
    field.setAccessible(true);
    // 假设属性的类型为一个String类型
    field.set(object, "Doctor");
    

    给属性设置值,为什么要在set函数中传入一个对象呢?因为目前使用的属性是属于某一对象。

    1. 获取属性的修饰符
    Field field = clazz.getDeclaredField("fieldName");
    String privStr = Modifier.toString(field.getModifiers());
    

    getModifiers()返回值为int类型,代表类、成员变量、方法的修饰符。
    其类型分别为:public, protected, private, static, final……

    1. 反射获取类中的方法
    Class clazz = Class.forName("com.doctor.reflect.ReflectTest"); 
    Object object = clazz.newInstance();
    // 第一个参数为反射要调用的函数名,后面的参数为反射函数中要传入的参数类型
    Method method = class.getDeclaredMethod("methodName", String.class, int.class);
    method.invoke(object, "Doctor", 18);
    

    Method中的invoke方法用于检测AccessibleObject的override属性是否为true。
    AccessibleObject是Method, Field, Constructor的父类,override属性默认为false,可通过调用setAccessible()方法改变override的值,如果设置为true,则表示可以忽略访问权限进行方法调用,注意是忽略访问权限的限制,而不是改变其访问权限

    注意事项

    1. getField 方法只能获取对象的public修饰的属性,并且也可以获取父类中的public属性,仅限于public属性
    2. getDeclaredField 方法能够获取到对象中所有修饰符修饰的属性,但是不能获取到父类中的任何属性
    3. 通过反射获取一个对象,可以使用Class.newInstance(),也可以使用Constructor.newInstance()。不同的之处在于:Class.newInstance()的使用受到了严格的限制,对应的Class中必须要有一个无参数的构造函数,并且这个无参的构造函数必须要有访问权限。这个访问权限是指:可以是public, protected, 或是默认包访问权限,但是不能使用private修饰而Constructor.newInstance()适用于任何访问类型的构造函数,无论是否有参数,只需要使用setAccessible()函数消除访问权限的限制即可。

    通过反射调用类的静态方法

    Class clazz = Class.forName("com.doctor.reflect.ReflectTest"); 
    Object object = clazz.newInstance();
    // 第一个参数为反射要调用的函数名,后面的参数为反射函数中要传入的参数类型
    Method method = class.getDeclaredMethod("staticMethodName", String.class, int.class);
    method.invoke(null, "Doctor", 18);
    

    反射调用类的静态方法与反射调用类的非静态方法的区别在于invoke()函数传递的第一个参数上,当反射调用静态方法时,第一个参数传入null,因为静态方法属于类本身,所以不需要传递某一特定对象,传递null即可。

    反射调用泛型方法

    public class GenericMethodTest<T> {
        
        public void genericMethod(T t) {
            System.out.println(t.getClass());
        }
    }
    ----------------------------------------------------------------------------------------------
    public class ReflectTest {
        
        public void reflect() {
            try {
                Class<?> clazz = Class.forName("com.test.GenericMethodTest");
                Object object = clazz.newInstance();
                Method method = clazz.getDeclaredMethod("genericMethod", Object.class);
                method.setAccessible(true);
                method.invoke(object, 1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    涉及到泛型擦除的知识,
    当一个函数中有泛型参数时,编译器会自动将其向上转型,T向上转型为Object,实际上genericMethod()函数中的T就向上转型为Object,所以genericMethod函数实际上是genericMethod(Object t), 所以在clazz.getDeclaredMethod方法中传递的参数类型为Object.class。

    相关文章

      网友评论

          本文标题:反射

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