無處不在的反射

本文不講反射的具體實現(xiàn)麻汰。

1.反射的原理 - class對象

11)class對象概述

編譯階段庐冯,編譯器將java代碼編譯為class文件吐绵。


class文件結(jié)構(gòu)大致長這樣

JVM在類加載階段彼哼,會將class文件中的信息轉(zhuǎn)為方法區(qū)的運行時數(shù)據(jù)对妄,同時在堆中創(chuàng)建一個代表方法區(qū)中對應數(shù)據(jù)入口的Class對象。通過這個Class對象沪羔,我們可以在運行時獲取到類中定義的實例構(gòu)造器饥伊、字段、方法等信息蔫饰。
反射的基本思路就是通過這個Class對象去獲取實例構(gòu)造器,創(chuàng)建實例對象愉豺。然后根據(jù)方法名獲取方法對象篓吁,通過method.invoke()來執(zhí)行方法。

1.2)class對象的獲取

class對象的獲取方式主要有:

  1. 通過全限定類名獲取
  2. 通過已有的實例對象來獲取它對應的Class對象
    這兩種方式創(chuàng)建過程都是蚪拦,如果要獲取的class已經(jīng)完成了類加載杖剪,Class對象存在于堆中,那么就獲取到這個對象驰贷;如果沒有盛嘿,則類加載創(chuàng)建出對應的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)字段次兆、方法、構(gòu)造器的獲取

通過Class對象锹锰,可以去訪問到類中定義的屬性芥炭。其中最常用的是構(gòu)造器、字段恃慧、方法园蝠。

1.3.1)構(gòu)造器獲取:

可以通過getConstructor方法來獲取構(gòu)造器痢士,無參的方法即調(diào)用默認無參構(gòu)造器彪薛,如果傳入?yún)?shù)類型的數(shù)組,如下面代碼所示,即可獲取到對應的帶參構(gòu)造器善延。如果在類中沒有對應的構(gòu)造器训唱,則拋出異常。

        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應該是內(nèi)存中原本Field的拷貝而不是本身训挡,因為獲取到的Field對象是可以修改的澳骤,設(shè)計者當然不希望因為程序員在某處改錯了而給其他使用堆內(nèi)存中該Field的代碼帶來不好的影響。
通過下面的代碼可以獲取到friend字段(Field)澜薄, 通過字段可以獲取到它的訪問權(quán)限修飾符为肮,如果具有訪問權(quán)限,還可以通過字段獲取某個具體實例對象該字段對應的屬性值肤京。

        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

以下兩個代碼片段將不會執(zhí)行成功颊艳,原因是friends是Man類的私有成員變量,通過getField只能獲取public權(quán)限的變量忘分。而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));
  • 獲取字段數(shù)組

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是對構(gòu)造器的抽象笛丙,一個構(gòu)造器對象至少應該包含如下信息:它所屬的類漾脂、參數(shù)類型數(shù)組、異常類型數(shù)組胚鸯、訪問修飾符骨稿。
構(gòu)造器的功能就是初始化一個實例對象。完成該功能的核心方法是newInstance方法蠢琳,該方法會創(chuàng)建一個實例對象啊终,并為對象的屬性賦值。至于如何創(chuàng)建的可以參考對象的創(chuàng)建與訪問定位傲须。

2.2)Field

Field是對字段的抽象蓝牲,它應當包含以下信息:定義它的類、字段名泰讽、字段的類型例衍、訪問修飾符昔期、以及操作實例對象中該字段的屬性的工具(比如為某個具體實例的字段設(shè)置值、獲取字段的值)
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是對方法的抽象硼一,可以是類方法或?qū)嵗椒ǎòǔ橄蠓椒ǎ?br> 方法除了定義它的類、方法名梦抢、訪問修飾符之外般贼,還應當包含方法返回值類型、參數(shù)類型數(shù)組奥吩、異常類型數(shù)組哼蛆,以及執(zhí)行某個具體實例對象中該方法的工具(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轉(zhuǎn)化為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創(chuàng)建bean并注入屬性

Mybatis創(chuàng)建方法返回對象并注入屬性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末霞赫,一起剝皮案震驚了整個濱河市腮介,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌端衰,老刑警劉巖叠洗,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旅东,居然都是意外死亡灭抑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門玉锌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來名挥,“玉大人,你說我怎么就攤上這事主守。” “怎么了榄融?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵参淫,是天一觀的道長。 經(jīng)常有香客問我愧杯,道長涎才,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任力九,我火速辦了婚禮耍铜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘跌前。我一直安慰自己棕兼,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布抵乓。 她就那樣靜靜地躺著伴挚,像睡著了一般靶衍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茎芋,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天颅眶,我揣著相機與錄音,去河邊找鬼田弥。 笑死涛酗,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的偷厦。 我是一名探鬼主播商叹,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沪哺!你這毒婦竟也來了沈自?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤辜妓,失蹤者是張志新(化名)和其女友劉穎枯途,沒想到半個月后粥诫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體察郁,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年呜象,在試婚紗的時候發(fā)現(xiàn)自己被綠了孽惰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晚岭。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖勋功,靈堂內(nèi)的尸體忽然破棺而出坦报,到底是詐尸還是另有隱情,我是刑警寧澤狂鞋,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布片择,位于F島的核電站,受9級特大地震影響骚揍,放射性物質(zhì)發(fā)生泄漏字管。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一信不、第九天 我趴在偏房一處隱蔽的房頂上張望嘲叔。 院中可真熱鬧,春花似錦抽活、人聲如沸硫戈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掏愁。三九已至歇由,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間果港,已是汗流浹背沦泌。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辛掠,地道東北人谢谦。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像萝衩,于是被迫代替她去往敵國和親回挽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 今天感恩節(jié)哎猩谊,感謝一直在我身邊的親朋好友千劈。感恩相遇!感恩不離不棄牌捷。 中午開了第一次的黨會墙牌,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,556評論 0 11
  • 彩排完,天已黑
    劉凱書法閱讀 4,197評論 1 3
  • 表情是什么暗甥,我認為表情就是表現(xiàn)出來的情緒喜滨。表情可以傳達很多信息。高興了當然就笑了撤防,難過就哭了虽风。兩者是相互影響密不可...
    Persistenc_6aea閱讀 124,427評論 2 7