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<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);
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容