Java反射知识重拾

什么是反射

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 {
    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 generic = (Generic) instance;
        System.out.println(generic.getT());

        // android
    }

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

泛型数据类型判断

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

public static void typeGuess(Type type) {
        if (type instanceof ParameterizedType) {
            // 表示参数化类型变量
            // 如 Map
            // 此方法可以获取, 其中参数的类型
            Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
        } else if (type instanceof GenericArrayType) {
            // 表示泛型类型的数组
            // 如 T[]
            // 如 Map[]
        } else if (type instanceof TypeVariable) {
            // 泛型类型变量, 无法直接实例化, 但可以用 Object将其实例化
            // 如 T
        } else if (type instanceof WildcardType) {
            // 通配符类型, 如果直接实例化
            // 如 List
        } 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反射知识重拾)