美文网首页
Java反射知识重拾

Java反射知识重拾

作者: JimmieYang | 来源:发表于2019-02-02 11:21 被阅读1252次

    什么是反射

    Reflection is a feature in the Java programming language. It allows an executing Java program to examine or "introspect" upon itself, and manipulate internal properties of the program. For example, it's possible for a Java class to obtain the names of all its members and display them.

    上面是 官方对 反射的介绍, 反射是Java编程语言的一个特性。 它允许正在执行的Java程序检查或“内省”自身,并操纵程序的内部属性.

    即 我们可以在运行时, 拿到加载到内存中类的所有信息, 如 字段,方法和构造函数等信息,并且能够操作其属性和方法.

    反射的作用

    一般在两种情况下使用反射.

    1. 程序解耦, 提高代码的灵活度.

    大部分开发各种通用框架或者开源库时, 很经常使用到 反射.

    1. 为了获取和修改私有构造函数的类, 或者类的私有字段和私有方法.

    由于某种需求, 我们需要拿到系统或者第三方库的私有数据, 可能可以通过反射获取.

    测试类

    这里先给出测试类, 后续反射示例都会使用到该类.

    package reflection;
    
    public class Ref {
        private static final String staticFinalField = "staticFinalField";
        private final        String finalField       = "finalField";
        private static       String staticField      = "staticField";
        private              String field            = "field";
    
        public Ref() {
        }
    
        private Ref(String field) {
            this.field = field;
        }
    
        private void setField(String field) {
            this.field = field;
        }
    
        private String getFinalField() {
            return finalField;
        }
    
        private static void setStaticField(String field) {
            staticField = field;
        }
    
        static class Inner {
        }
    }
    
    

    Class对象

    java中存在两种对象, 一种是 类对象, 一种是 实例对象.
    实例对象通常是通过 new , 反射 , clone方法, 枚举初始化 以及 反序列创建出来的.
    而类对象(Class)则是由 JVM 在类加载阶段生成的对象,用于保存类信息.
    类对应Class对象 在JVM中是有且仅有一个与之对应.

    而我们的反射信息, 就首先需要先拿到 Class对象.

    我们可以通过三种途径拿到 Class对象.

    1. 类名.class
        // 使用 类.class
        Class<?> klass = Ref.class;
    
    1. Class.forName
        // 使用forName获取类对象
        Class<?> klass = Class.forName("reflection.Ref");
        // 注意, 如果调用的是静态内部类, forName时需要 用$连接
        klass = Class.forName("reflection.Ref$Inner");
    
    1. 对象实例.getClass
        // 使用 对象.getClass
        Object object = new Ref();
        klass = object.getClass();
    

    反射创建实例

    1. 如果类的构造器是公开且无参构造函数,则直接使用Class.newInstance
        // 1. 对于 默认构造函数,且是public可以直接使用Class调用
        Class<?> klass = Class.forName("reflection.Ref");
        Object object = klass.newInstance();
    
    1. 使用Constructor构造器可以创建带参且私有的构造函数
        // 使用 Constructor , 可调用私有且带参的
        // getConstructor() 只能调用,显式声明且公共的构造函数
        // getDeclaredConstructor() 可以调用所有的构造器,包括私有的
        Class<?> klass = Class.forName("reflection.Ref");
        // 设置带参的构造器
        Constructor<?> ctor = klass.getDeclaredConstructor(String.class);
        // 设置私有构造可访问
        ctor.setAccessible(true);
        // 传入构造函数的参数
        Object object = ctor.newInstance("ctorField");
    

    注意 : getDeclaredConstructor方法, 可以获取私有的构造器,在操作私有的构造器时,需要调用 setAccessible(true)方法,来使私有构造方法可达.

    1. 获取类的所有构造函数信息
        // 获取所有构造函数
        // getDeclaredConstructors() 可以获取所有包括私有的构造函数
        // getConstructors() 只能获取公共的构造函数
        Constructor<?>[] all = klass.getDeclaredConstructors(); 
        for (Constructor<?> constructor : all) {
            // 获取构造函数的参数信息
            Class<?>[] types = constructor.getParameterTypes();
            for (Class<?> type : types) {
                System.out.print("type : " + type.getName() + ",");
            }
            System.out.println("===");
        }
    

    反射操作字段

    1. 操作私有字段
            Class<?> klass  = Class.forName("reflection.Ref");
            Object   object = klass.newInstance();
            // 修改私有字段
            // getField() 获取公共的字段, 包含父类的公共字段
            // getDeclaredField() 获取私有以及公共字段,但不包括父类字段
            // 可以设置 final 对象的值
            Field field = klass.getDeclaredField("field");
            field.setAccessible(true);
            // 设置值
            field.set(object, "setting");
            // 输出 object对象的 field 字段的值 值为 setting
            System.out.println(field.get(object));
    

    注释 : getDeclaredField 可以获取当前对象的私有字段, 但是无法获取其父类的字段.
    getField 虽然只能获取公共字段,但是可以拿到父类的公共字段.

    1. 操作私有静态字段
            Class<?> klass  = Class.forName("reflection.Ref");
            // 修改私有静态字段
            // 不可以设置 static final 的字段
            field = klass.getDeclaredField("staticField");
            // 私有字段,设置可达
            field.setAccessible(true);
            // 设置静态值, 无需传实例
            field.set(null, "setStatic");
            // 输出静态字段 # setStatic
            System.out.println(field.get(null));
    

    注释 : 静态字段,是属于类的字段,无需实例的支持,所以 field.set(Object obj, Object value),第一个参数需要的实例直接传null. field.get(Object obj)参数直接传null.

    1. 获取所有字段
            Class<?> klass = Class.forName("reflection.Ref");
            // 获取所有字段
            // getFields 获取所有的公共字段,包括父类的公共字段
            // getDeclaredFields 获取当前类的所有字段,包括私有字段,但不包括父类字段
            Field[] fields = klass.getDeclaredFields();
            for (Field f : fields) {
                System.out.println(f.getName());
            }
    

    4. 操作final字段

    final修饰的变量比较特殊, 可以分为两种类型.
    一种是修饰 字面量(不可变类型), 如基本类型和String对象(但包括基本类型的装箱对象以及new String()产生的对象).
    另一种就是 修饰 普通的对象.

    final 修饰字面量时, 将被内联, 内联的字段反射修改无效.

    观察以下代码:

    public class ReflectionDemo {
        public static void main(String[] args) throws Exception {
            Class<?> klass = Final.class;
            Final    obj   = (Final) klass.newInstance();
    
            Field finalField = klass.getDeclaredField("finalField");
            // 如果无法修改final字段,尝试去掉 final 修饰符
            // removeFinalModifier(finalField);
            finalField.setAccessible(true);
            finalField.set(obj, "setFinal");
    
            Field inlineField = klass.getDeclaredField("inlineFinalField");
            // removeFinalModifier(inlineField);
            inlineField.setAccessible(true);
            inlineField.set(obj, "setInline");
    
            obj.print();
        }
    
        static class Final {
            // 编译时,内联
            private final String inlineFinalField = "inlineFinalField";
            // 不会被内联
            private final String finalField       = new String("finalField");
    
            void print() {
                // 将被内联为 System.out.println("inlineFinalField");
                System.out.println(inlineFinalField);
                System.out.println(finalField);
            }
        }
    
        // 去除对字段的 final 修饰符
        static void removeFinalModifier(Field field) throws Exception {
            Field modifierField = Field.class.getDeclaredField("modifiers");
            modifierField.setAccessible(true);
            modifierField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        }
    }
    // inlineFinalField
    // setFinal
    

    final字段被内联时, 使用到该字段的地方,直接被替换为 其字面量的值, 所以即使修改了 变量也无济于事.

    因此, 只有当 final 修饰的是 普通的对象实例变量时, 反射该字段 才有意义.

    操作 static final 变量

        private static void staticFinalFiledTest() throws Exception {
            Class<?> klass = Ref.class;
            // 静态字段无需对象实例
            Field field = klass.getDeclaredField("staticFinalField");
            field.setAccessible(true);
            // 1. 读取 private static final 字段
            String value = (String) field.get(null);
            System.out.println(value);
            // 2. 修改 private static final 字段
            field.set(null, "setTest");
            System.out.println(field.get(null));
        }
    
        //staticFinalField
        //Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field
    

    注释 : 反射 final static 修饰的变量, 可以读取,但是无法修改. 修改时,将抛出Can not set static final int field异常.

    反射操作方法

    1. 操作私有方法
            Class<?> klass  = Class.forName("reflection.Ref");
            Object   object = klass.newInstance();
            // 操作私有方法
            // 传入方法名, 和 方法参数类型
            Method method = klass.getDeclaredMethod("setField", String.class);
            method.setAccessible(true);
            // 执行方法, 传入实例 和 方法参数值, 获取到方法返回值
            String result = (String) method.invoke(object, "set field test");
            System.out.println(result);
            // set field test
    
    1. 操作静态方法
            Class<?> klass  = Class.forName("reflection.Ref");
            // 私有静态方法
            method = klass.getDeclaredMethod("setStaticField", String.class);
            method.setAccessible(true);
            result = (String) method.invoke(null, "set static field");
            System.out.println(result);
            // set static field
    

    注释 : 静态方法无需传入实例, 直接传null.

    1. 获取所有的方法
            // 获取所有方法
            // getMethods() 获取所有的公共方法,包含父类的公共方法
            // getDeclaredMethods() 获取所有的方法,包括私有方法, 但不包括继承的方法
            Method[] methods = klass.getDeclaredMethods();
            for (Method m : methods) {
                // 获取返回类型
                Class<?> returnType = m.getReturnType();
                // 获取参数类型
                Class<?>[] params = m.getParameterTypes();
            }
    

    对泛型的反射

    反射创建泛型对象

    public class Generic<T> {
        private T t;
    
        private Generic(T t) {
            this.t = t;
        }
    
        public T getT() {
            return t;
        }
    }
    
        private static void newGenericTest() throws Exception {
            Class<?> klass = Class.forName("reflection.Generic");
            // 由于泛型擦除的原因, 最终会被转为Object
            // 所以构造函数传入 Object类对象
            Constructor<?> ctor = klass.getDeclaredConstructor(Object.class);
            ctor.setAccessible(true);
            // 由于Object是所有类的父类,所以这里可以传入任意对象的实例.
            String ctorArg  = "android";
            Object instance = ctor.newInstance(ctorArg);
    
            // 转化为目标对象,对应的目标泛型类型
            Generic<String> generic = (Generic<String>) instance;
            System.out.println(generic.getT());
    
            // android
        }
    

    由于泛型擦除的原因,传入的泛型只能用 Object代替. (T extends Parent,这种形式,则由Parent代替.)

    泛型数据类型判断

    根据 获取 类的 Type 可以判断是否是泛型变量,并且判断是否可以直接实例化.

    public static void typeGuess(Type type) {
            if (type instanceof ParameterizedType) {
                // 表示参数化类型变量
                // 如 Map<Integer,String>
                // 此方法可以获取, 其中参数的类型
                Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
            } else if (type instanceof GenericArrayType) {
                // 表示泛型类型的数组
                // 如 T[]
                // 如 Map<Integer,String>[]
            } else if (type instanceof TypeVariable) {
                // 泛型类型变量, 无法直接实例化, 但可以用 Object将其实例化
                // 如 T
            } else if (type instanceof WildcardType) {
                // 通配符类型, 如果直接实例化
                // 如 List<? extends Integer>
            } else if (type instanceof Class) {
                // 普通类型,可以直接实例化
                // 如 String
            }
        }
    

    获取泛型类型的方式

        public static void getGenericType() throws Exception {
            Class<?> klass = XXX.class;
    
            // 1. 类变量
            Field field = klass.getDeclaredField("xxx");
            // 获取泛型的类型
            Type type = field.getGenericType();
    
            // 2. 方法的返回值
            Method method = klass.getDeclaredMethod("xxx");
            type = method.getGenericReturnType();
    
            // 3. 方法的参数
            Type[] types = method.getGenericParameterTypes();
    
            // 4. 构造函数的参数
            Constructor<?> ctor = klass.getDeclaredConstructor();
            types = ctor.getGenericParameterTypes();
    
            // 判断参数类型
            typeGuess(type);
        }
    

    相关文章

      网友评论

          本文标题:Java反射知识重拾

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