无处不在的反射

本文不讲反射的具体实现。

1.反射的原理 - class对象

11)class对象概述

编译阶段,编译器将java代码编译为class文件。


class文件结构大致长这样

JVM在类加载阶段,会将class文件中的信息转为方法区的运行时数据,同时在堆中创建一个代表方法区中对应数据入口的Class对象。通过这个Class对象,我们可以在运行时获取到类中定义的实例构造器、字段、方法等信息。
反射的基本思路就是通过这个Class对象去获取实例构造器,创建实例对象。然后根据方法名获取方法对象,通过method.invoke()来执行方法。

1.2)class对象的获取

class对象的获取方式主要有:

  1. 通过全限定类名获取
  2. 通过已有的实例对象来获取它对应的Class对象
    这两种方式创建过程都是,如果要获取的class已经完成了类加载,Class对象存在于堆中,那么就获取到这个对象;如果没有,则类加载创建出对应的Class对象。 它们获取到的对象都是同一个(单例)。
package reflect;

import java.lang.Class;

public class Solution {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clzHuman = Class.forName("reflect.Human"); //get Class object via Class.forName(name)
        
        Human human = new Human("张三");
        Class<? extends Human> aClass = human.getClass(); // get Class object via instance object
        System.out.println(aClass == clzHuman);  // true
    }
}

1.3)字段、方法、构造器的获取

通过Class对象,可以去访问到类中定义的属性。其中最常用的是构造器、字段、方法。

1.3.1)构造器获取:

可以通过getConstructor方法来获取构造器,无参的方法即调用默认无参构造器,如果传入参数类型的数组,如下面代码所示,即可获取到对应的带参构造器。如果在类中没有对应的构造器,则抛出异常。

        Class<?> clzMan = Class.forName("reflect.Man");
        Constructor<?> constructor = clzMan.getConstructor(new Class[]{String.class});
        Man man = (Man)constructor.newInstance("Lonely Man");
        System.out.println(man);
        // 输出man:{name=Lonely Man, weight=0.0, friends=null, age=0, height=0}

1.3.2) 字段获取

  • 通过变量名获取字段

Class.getField(name)返回已加载类声明的所有public成员变量的Field对象,包括从父类继承过来的成员变量。
Class.getDeclaredField(name)返回当前类所有成员变量。
返回的Field应该是内存中原本Field的拷贝而不是本身,因为获取到的Field对象是可以修改的,设计者当然不希望因为程序员在某处改错了而给其他使用堆内存中该Field的代码带来不好的影响。
通过下面的代码可以获取到friend字段(Field), 通过字段可以获取到它的访问权限修饰符,如果具有访问权限,还可以通过字段获取某个具体实例对象该字段对应的属性值。

        Field friendField = clzMan.getDeclaredField("friends");
        friendField.setAccessible(true);
        System.out.println(friendField.getName() + ": " +friendField.get(man));
        // 验证返回的Field是拷贝
        Field friendField1 = clzMan.getDeclaredField("friends");
        System.out.println(friendField == friendField1);  // false
        System.out.println(friendField1.getName() + ": " +friendField1.get(man)); //抛出IllegalAccessException

以下两个代码片段将不会执行成功,原因是friends是Man类的私有成员变量,通过getField只能获取public权限的变量。而age不是当前类定义的变量,通过Class<Man>是不可以获取的。

        //抛出异常
        Field friendField = clzMan.getField("friends");
        friendField.setAccessible(true);
        System.out.println(friendField + ": " + friendField.get(man));
        //抛出异常
        Field ageField = clzMan.getDeclaredField("age");
        ageField.setAccessible(true);
        System.out.println(ageField + ": " +ageField.get(man));

如果要获取age字段,那么必须先获取到Class<Human>对象,然后通过这个对象来获取。

        先获取humanClass,然后再通过humanClass获取age字段
        Class<?> humanClass = clzMan.getSuperclass();
        Field ageField = humanClass.getDeclaredField("age");
        ageField.setAccessible(true);
        System.out.println(ageField.getName()+": " +ageField.get(man));
  • 获取字段数组

getFields()方法将返回已加载类声明的所有public成员变量的Field对象,包括从父类继承过来的成员变量。
getDeclaredFields()可以用来返回当前类声明的所有成员变量

        Field[] fields = clzMan.getFields();
        for (Field field : fields) {
            String fieldNames = "";
            fieldNames += field.getName() + ", ";
            System.out.println(fieldNames); // 打印""
        }

        Field[] declaredFields = clzMan.getDeclaredFields();
        for (Field field : declaredFields) {
            String fieldNames = "";
            fieldNames += field.getName() + ", ";
            System.out.println(fieldNames); // 打印"friends,"
        }

1.3.3)方法对象的获取

与获取字段类似


2.三个重要的类:Field, Method,Constructor

2.1)Constructor

Constructor是对构造器的抽象,一个构造器对象至少应该包含如下信息:它所属的类、参数类型数组、异常类型数组、访问修饰符。
构造器的功能就是初始化一个实例对象。完成该功能的核心方法是newInstance方法,该方法会创建一个实例对象,并为对象的属性赋值。至于如何创建的可以参考对象的创建与访问定位。

2.2)Field

Field是对字段的抽象,它应当包含以下信息:定义它的类、字段名、字段的类型、访问修饰符、以及操作实例对象中该字段的属性的工具(比如为某个具体实例的字段设置值、获取字段的值)
Field最核心的功能是get/set某个具体实例该字段的值。

/**Sets the field represented by this {@code Field} object on the pecified object argument to the specified new value.
*/
    @CallerSensitive
    public void set(Object obj, Object value) 
        throws IllegalArgumentException, IllegalAccessException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        getFieldAccessor(obj).set(obj, value);
    }
/**Returns the value of the field represented by this {@code Field}, onthe specified object 
*/
    @CallerSensitive
    public Object get(Object obj)
        throws IllegalArgumentException, IllegalAccessException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        return getFieldAccessor(obj).get(obj);
    }

2.3) Method

Method是对方法的抽象,可以是类方法或实例方法(包括抽象方法)
方法除了定义它的类、方法名、访问修饰符之外,还应当包含方法返回值类型、参数类型数组、异常类型数组,以及执行某个具体实例对象中该方法的工具(MethodAccessor).
核心方法是invoke方法

    @CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }
method.invoke流程

常见的使用方式

入门demo

使用反射写一个将Map转化为Man对象的方法:

package reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

// 解析工具类
public class  Util <T>{
     public Object parseObject(Map<String, ?> map, Class<?> objectClass) throws Exception {
        assert objectClass != null;
        Constructor<?> constructor = objectClass.getConstructor();
        Object o = constructor.newInstance();
        while (objectClass != null){
            for (String fieldName : map.keySet())
            {
                Field field = null;
                try{
                    field = objectClass.getDeclaredField(fieldName);
                    Class<?> type = field.getType();
                    String typeName = type.getName();
                    String methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
                    Method method = objectClass.getDeclaredMethod(methodName, type);
                    if (type.isPrimitive() || typeName.equals("java.lang.String")){
                        method.invoke(o, map.get(fieldName));
                    }
                    // 使用switch ...case判断type该如何解析更好,这里是图demo写起来省事
                    // other types like array... are ignored
                    else {
                        Map sub = (Map)map.get(fieldName);
                        Object o1 = parseObject(sub, type);
                        method.invoke(o, o1);
                    }
                } catch (Exception e){}//e.printStackTrace();
            }


            Class<?> superclass = objectClass.getSuperclass();
            objectClass = superclass;
        }
        return o;
    }
}
//Man
public class Man<T> extends Human {
    private Human bestFriend;

    public Man() {
    }

    public Man(String name) {
        super(name);
    }

    public Human getBestFriend() {
        return bestFriend;
    }

    public void setBestFriend(Human friend) {
        this.bestFriend = friend;
    }

// Solution主方法所在类
public class Solution {
    public static void main(String[] args) throws Exception {
        Class<?> clzMan = Class.forName("reflect.Man");
        Constructor<?> constructor = clzMan.getConstructor(new Class[]{String.class});
        Man man = (Man)constructor.newInstance("Lonely Man");
        HashMap hashMap = new HashMap();
        hashMap.put("age", 10);
        hashMap.put("name", "zhangsan");
        HashMap friendMap = new HashMap();
        friendMap.put("age", 12);
        friendMap.put("name", "lisi");
        hashMap.put("bestFriend",friendMap);
        System.out.println("hashmap: " + hashMap);
        Util util = new Util();
        Object o = util.parseObject(hashMap, Man.class);
        System.out.println("man: " + o);
hashmap: {bestFriend={name=lisi, age=12}, name=zhangsan, age=10}
man: {bestFriend=Human{name='lisi', age=12, height=0, weight=0.0}, name=zhangsan, weight=0.0, age=10, height=0}

Json工具

SpringIoc创建bean并注入属性

Mybatis创建方法返回对象并注入属性

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

推荐阅读更多精彩内容

  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,548评论 0 11
  • 彩排完,天已黑
    刘凯书法阅读 4,182评论 1 3
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 123,993评论 2 7