(轉(zhuǎn)載)golang 中的反射

我們定義的一個(gè)變量,不管是基本類型int,還是一個(gè)結(jié)構(gòu)體Student,我們都可以通過(guò)reflect.TypeOf()獲取他的反射類型Type苍凛,也可以通過(guò)reflect.ValueOf()去獲取他的反射值Value
我們學(xué)習(xí)反射,其實(shí)就是學(xué)習(xí)如何通過(guò)變量兵志,去取得reflect.Type或者reflect.Value醇蝴;再使用得到的Type以及Value,反過(guò)來(lái)對(duì)變量進(jìn)行操作
弄明白了這個(gè)道理想罕,那一切都將變得簡(jiǎn)單
剩下的悠栓,我們只是需要去學(xué)習(xí)reflect包中提供的方法。當(dāng)我們需要要怎么操作變量按价,就使用其提供的對(duì)應(yīng)方法即可

反射的注意事項(xiàng)與細(xì)節(jié)

Type與Kind的區(qū)別

Type是類型惭适,Kind是類別,聽(tīng)起來(lái)有點(diǎn)繞楼镐,他們之間的關(guān)系為Type是Kind的子集
如果變量是基本類型癞志,那么Type與Kind得到的結(jié)果是一致的,比如變量為int類型框产,Type與Kind的值相等凄杯,都為int
但當(dāng)變量為結(jié)構(gòu)體時(shí),Type與Kind的值就不一樣了
我們來(lái)看個(gè)實(shí)際案例

func main() {
    var emp Employee
    emp = Employee{
        Name: "naonao",
        Age:  99,
    }
    rVal := reflect.ValueOf(emp)
    log.Printf("Kind is %v ,Type is %v",
        rVal.Kind(),
        rVal.Type())
    // Kind is struct ,Type is main.Employee
}

可以看到秉宿,Kind的值是struct,而Type的值是包名.Employee

在變量與reflect.Value之間切換

變量可以轉(zhuǎn)換成interface{}之后戒突,再轉(zhuǎn)換成reflect.Value類型,既然空接口可以轉(zhuǎn)換成Value類型描睦,那么自然也可以反過(guò)來(lái)轉(zhuǎn)換成變量
用個(gè)表達(dá)式來(lái)表示膊存,就如下所示
變量<----->interface{}<----->reflect.Value
利用空接口來(lái)進(jìn)行中轉(zhuǎn),這樣變量與Value之間就可以實(shí)現(xiàn)互相轉(zhuǎn)換了
下面我們?cè)僬f(shuō)如何用代碼實(shí)現(xiàn)轉(zhuǎn)換

通過(guò)反射獲取變量本身的值

這里我們要注意一下,reflect.ValueOf()得到的值是reflect.Value類型隔崎,并不是變量本身的值

var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)

這段代碼會(huì)報(bào)錯(cuò)invalid operation: num + rVal (mismatched types int and reflect.Value)
很明顯今艺,rVal是屬于reflect.Value類型,不能與int類型相加
那怎樣才能獲得它本身的值呢爵卒?
如果是基本類型虚缎,比如var num int,那么使用reflect包里提供的轉(zhuǎn)換方法即可reflect.ValueOf(num).Int()
或者是float,那就調(diào)用reflect.ValueOf(num).float()技潘,如果是其它的基本類型,需要的時(shí)候去文檔里面找找即可
但如果是我們自己定義的結(jié)構(gòu)體千康,因?yàn)閞eflect包無(wú)法確定我們自己定義了什么結(jié)構(gòu)體享幽,所以本身并不會(huì)帶有結(jié)構(gòu)體轉(zhuǎn)換的方法,那么我們只能通過(guò)類型斷言來(lái)進(jìn)行轉(zhuǎn)換
也就是上面說(shuō)的拾弃,利用空接口進(jìn)行中轉(zhuǎn)值桩,再利用斷言進(jìn)行類型轉(zhuǎn)換

使用反射來(lái)遍歷結(jié)構(gòu)體的字段值,并獲取結(jié)構(gòu)體的tag標(biāo)簽
// Employee 員工
type Employee struct {
    Name string `json:"emp_name"`
    Age  int    `json:"emp_age"`
    Sex  int
}

// GetSum 返回兩數(shù)之和
func (e *Employee) GetSum(n1, n2 int) int {
    return n1 + n2
}

// Set 接受值豪椿,給結(jié)構(gòu)體e賦值
func (e *Employee) Set(name string, age, sex int) {
    e.Name = name
    e.Age = age
    e.Sex = sex
}

// Print 打印結(jié)構(gòu)體*Employee 
func (e *Employee) Print() {
    log.Print("======Start======")
    log.Print(e)
    log.Print("======End======")
}

//使用反射來(lái)遍歷結(jié)構(gòu)體的字段值奔坟,并獲取結(jié)構(gòu)體的tag標(biāo)簽
//先來(lái)看個(gè)常規(guī)用法
// GetStruct 獲取結(jié)構(gòu)體的字段及tag
func GetStruct(i interface{}) {
    rType := reflect.TypeOf(i)
    rVal := reflect.ValueOf(i)

    kd := rVal.Kind()

    // 如果是傳進(jìn)來(lái)的是指針類型
    // 則獲取指針值
    if kd == reflect.Ptr {
        rType = rType.Elem()
        rVal = rVal.Elem()
        kd = rVal.Kind()
    }

    if kd != reflect.Struct {
        log.Panicf("Kind is %v not struct ", kd)
    }
    // 獲取結(jié)構(gòu)體的字段數(shù)
    sNum := rVal.NumField()
    log.Printf("Struct has %v fields ", sNum)
    // 遍歷結(jié)構(gòu)體的所有字段
    for i := 0; i < sNum; i++ {
        log.Printf("Field %d value is %v", i, rVal.Field(i))
        // 獲取Struct的tag,使用Type類型獲取
        tag := rType.Field(i).Tag.Get("json")
        if tag == "" {
            log.Printf("Field %d hasn't tag  %v ", i, tag)
            continue
        }
        log.Printf("Field %d tag is %v ", i, tag)
    }
}
//復(fù)制代碼我們定義一個(gè)方法GetStruct(i interface{})搭盾,因?yàn)槿雲(yún)⑹莍nterface{}類型咳秉,所以這個(gè)方法可以接收
//并處理所有的數(shù)據(jù)類型。這就是反射的牛逼之處了
//遺憾的是鸯隅,反射的性能比較低澜建。后面咱們對(duì)性能進(jìn)行分析時(shí)再拿出來(lái)聊聊
//測(cè)試用例如下
func TestGetStruct(t *testing.T) {
    emp := &Employee{}
    emp.Set("鬧鬧", 99, 0)
    GetStruct(emp)
}
//這個(gè)函數(shù)接受的參數(shù)是interface,也就是說(shuō)蝌以,通過(guò)這個(gè)函數(shù)炕舵,不管入?yún)鬟f了什么樣的結(jié)構(gòu)體,我們可以知道這個(gè)結(jié)構(gòu)體有什么標(biāo)簽跟畅,
//有幾個(gè)方法獲取tag標(biāo)簽的用處就是對(duì)我們的結(jié)構(gòu)體進(jìn)行序列化時(shí)使用咽筋,將結(jié)構(gòu)體的字段名變成我們需要的別名
//想深入了解的童鞋,可以參考下encoding/json包的使用方式

獲取并調(diào)用結(jié)構(gòu)體的方法
// CallMethod 調(diào)用結(jié)構(gòu)體方法
// i : 傳入的struct
// methodByName : 調(diào)用結(jié)構(gòu)體的方法名
func CallMethod(i interface{}, methodByName string) {
    rVal := reflect.ValueOf(i)
    rType := reflect.TypeOf(i)
    log.Printf("Type is %v Kind is %v", rType, rType.Kind())

    // 獲取結(jié)構(gòu)體有多少個(gè)方法
    numOfMethod := rVal.NumMethod()
    log.Printf("Struct has %d method", numOfMethod)
    // 聲明Value數(shù)組
    var params []reflect.Value
    // 聲明一個(gè)Value類型徊件,用于接收方法
    var method reflect.Value

    if methodByName == "GetSum" {
        // 調(diào)用方法時(shí)的參數(shù)
        params = append(params, reflect.ValueOf(10))
        params = append(params, reflect.ValueOf(88))

    }
    if methodByName == "Set" {
        // 調(diào)用方法時(shí)的參數(shù)
        params = append(params, reflect.ValueOf("鬧鬧吃魚(yú)"))
        params = append(params, reflect.ValueOf(18))
        params = append(params, reflect.ValueOf(0))
    }
    // 獲取方法
    method = rVal.MethodByName(methodByName)
    if !method.IsValid() {
        // 如果結(jié)構(gòu)體不存在此方法奸攻,輸出Panic
        log.Panic("Method is invalid")
    }
    result := method.Call(params)
    if len(result) > 0 {
        // 如果函數(shù)存在返回值,則打印第一條
        log.Println("Call result is ", result[0])
    }
}
//這里值得注意一點(diǎn)的就是虱痕,我們通過(guò)反射的Call去調(diào)用函數(shù)舞箍,傳入的參數(shù)的類型是reflect.Value類型,并不是我們定義函數(shù)時(shí)的int類型
//所以在調(diào)用函數(shù)時(shí)傳入的參數(shù)需要進(jìn)行一個(gè)類型轉(zhuǎn)換
//給你們附上測(cè)試用例皆疹,你們可以自己調(diào)試跑跑疏橄,會(huì)發(fā)現(xiàn),不管你傳的結(jié)構(gòu)體的字段是什么,我都進(jìn)行統(tǒng)一處理了
func TestCallMethod(t *testing.T) {
    emp := &Employee{}
    emp.Set("鬧鬧", 99, 0)
    emp.Print()
    CallMethod(emp, "Set")
    emp.Print()
}

修改字段值
// ModifyField 修改字段值
func ModifyField(i interface{}, filedName string) {
    rVal := reflect.ValueOf(i)
    filed := rVal.Elem().FieldByName(filedName)
    if !filed.IsValid() {
        log.Panic("filedName is invalid")
    }
    filed.SetString("鬧鬧")
}
復(fù)制代碼運(yùn)行時(shí)修改結(jié)構(gòu)體的字段捎迫,主要就是做到一個(gè)通用性晃酒,比如上述的例子,不管是什么結(jié)構(gòu)體
依然附上測(cè)試用例
func TestModifyField(t *testing.T) {
    emp := &Employee{}
    ModifyField(emp, "Name")
}
復(fù)制代碼不管傳入的結(jié)構(gòu)體是什么窄绒,只要包含了filedName(我們指定的字段名)贝次,我們就可以對(duì)其進(jìn)行值的更改
假如我們有100個(gè)結(jié)構(gòu)體,需要對(duì)name字段進(jìn)行修改彰导,通過(guò)反射的機(jī)制蛔翅,我們代碼的耦合度將大大的降低
定義適配器,用作統(tǒng)一處理接口
// Bridge 適配器
// 可以實(shí)現(xiàn)調(diào)用任意函數(shù)
func Bridge(call interface{}, args ...interface{}) {
    var (
        function reflect.Value
        inValue  []reflect.Value
    )
    n := len(args)
    // 將參數(shù)轉(zhuǎn)換為Value類型
    inValue = make([]reflect.Value, n)
    for i := 0; i < n; i++ {
        inValue[i] = reflect.ValueOf(args[i])
    }
    // 獲得函數(shù)的Value類型
    function = reflect.ValueOf(call)
    // 傳參位谋,調(diào)用函數(shù)
    function.Call(inValue)
}
//寫了個(gè)測(cè)試用例山析,函數(shù)是我們?cè)谡{(diào)用Bridge前就已經(jīng)定義好了
func TestBridge(t *testing.T) {
    call1 := func(v1, v2 int) {
        log.Println(v1, v2)
    }
    call2 := func(v1, v2 int, str string) {
        log.Println(v1, v2, str)
    }

    Bridge(call1, 1, 2)
    Bridge(call2, 2, 3, "callTest")
}

//兩個(gè)函數(shù)是不同的函數(shù),但是都可以通過(guò)Bridge進(jìn)行執(zhí)行
//適配器有什么用呢掏父?如果不知道的童鞋笋轨,可以去看看設(shè)計(jì)模式「適配器模式」
//因?yàn)楸酒皇钦f(shuō)如何在實(shí)戰(zhàn)中應(yīng)用反射,所以這里就不講解設(shè)計(jì)模式了
//使用反射創(chuàng)建赊淑,并操作結(jié)構(gòu)體
// CreateStruct 使用反射創(chuàng)建結(jié)構(gòu)體
// 并給結(jié)構(gòu)體賦值
func CreateStruct(i interface{}) *Employee {
    var (
        structType  reflect.Type
        structValue reflect.Value
    )
    // 獲取傳入結(jié)構(gòu)體指向的Type類型
    structType = reflect.TypeOf(i).Elem()
    // 創(chuàng)建一個(gè)結(jié)構(gòu)體
    // structValue持有一個(gè)指向類型為Type的新申請(qǐng)的指針
    structValue = reflect.New(structType)
    // 轉(zhuǎn)換成我們要?jiǎng)?chuàng)建的結(jié)構(gòu)體
    modle := structValue.Interface().(*Employee)
    // 取得structValue指向的值
    structValue = structValue.Elem()
    // 給結(jié)構(gòu)體賦值
    structValue.FieldByName("Name").SetString("鬧鬧吃魚(yú)")
    structValue.FieldByName("Age").SetInt(100)
    return modle
}
//使用方式就看看測(cè)試用例
func TestCreateStruct(t *testing.T) {
    emp := &Employee{
        Name: "NaoNao",
        Age:  18,
        Sex:  1,
    }
    emp.Print()// &{NaoNao 18 1}
    newEmp := CreateStruct(emp)
    newEmp.Print()// &{鬧鬧吃魚(yú) 100 0}
}
//可能你會(huì)問(wèn)爵政,CreateStruct的入?yún)⒉皇莍nterface{}嗎?可為什么我傳一個(gè)任意的結(jié)構(gòu)體陶缺,卻要給返回一個(gè)指定的結(jié)構(gòu)體呢钾挟?就不能我傳什么結(jié)構(gòu)體進(jìn)去,就返回什么結(jié)構(gòu)體出來(lái)嗎饱岸?
//理想總是豐滿的等龙,現(xiàn)實(shí)卻是非常骨感,雖然我們反射的方法實(shí)現(xiàn)伶贰,都是將入?yún)憺閕nterface{}蛛砰,但使用反射并不是意味著我們一定就寫了一個(gè)萬(wàn)能的程序
//我們不管是把Value轉(zhuǎn)為結(jié)構(gòu)體,還是轉(zhuǎn)為基本類型黍衙,我們都需要在編譯前確定轉(zhuǎn)換后的類型
//換句話說(shuō)泥畅,只要我們?cè)谶\(yùn)行時(shí)牽扯到類型的轉(zhuǎn)換,我們都需要各種if來(lái)判斷是否能轉(zhuǎn)換成我們需要的類型
通過(guò)反射來(lái)修改變量
先來(lái)看看代碼如何實(shí)現(xiàn)

func main() {
    var num = 1
    modifyValue(&num)// 傳遞地址
    log.Printf("num is %v", num)// num is 20
}

func modifyValue(i interface{}) {
    rVal := reflect.ValueOf(i)
    rVal.Elem().SetInt(20)
}

細(xì)心的你肯定發(fā)現(xiàn)了一點(diǎn)異常琅翻,函數(shù)接收的參數(shù)不再是值了位仁,而是接受了一個(gè)指針地址
改變值的時(shí)候,先調(diào)用了Elem()方法方椎,再進(jìn)行了一個(gè)SetInt()的操作
為什么直接傳值不行呢聂抢?因?yàn)閞eflect包中提供的所有修改變量值的方法,都是對(duì)指針進(jìn)行的操作
那為什么還要先使用Elem()呢棠众?因?yàn)镋lem()的作用琳疏,就是取得指針地址所對(duì)應(yīng)的值有决,取到值了,我們才能對(duì)值進(jìn)行修改
總不可能連值都沒(méi)拿到手空盼,就想著去改值吧书幕?

理解reflect.Value.Elem()
關(guān)于Elem()的使用可以簡(jiǎn)單的理解為
num := 1
prt *int := &num // 獲取num的指針地址
num2 := *ptr // 從指針處取值

因?yàn)槲覀儌鬟f了一個(gè)地址,所以我們要先拿到這個(gè)地址的指針揽趾,再通過(guò)指針去取得所對(duì)應(yīng)的值
reflect包底層實(shí)現(xiàn)就是基于這個(gè)原理台汇,只是它的底層代碼加了較多的判斷,用來(lái)保證穩(wěn)定性

golang的反射與實(shí)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末篱瞎,一起剝皮案震驚了整個(gè)濱河市苟呐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俐筋,老刑警劉巖牵素,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異校哎,居然都是意外死亡两波,警方通過(guò)查閱死者的電腦和手機(jī)瞳步,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門闷哆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人单起,你說(shuō)我怎么就攤上這事抱怔。” “怎么了嘀倒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵屈留,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我测蘑,道長(zhǎng)灌危,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任碳胳,我火速辦了婚禮勇蝙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挨约。我一直安慰自己味混,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布诫惭。 她就那樣靜靜地躺著翁锡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪夕土。 梳的紋絲不亂的頭發(fā)上馆衔,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼哈踱。 笑死荒适,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的开镣。 我是一名探鬼主播刀诬,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼邪财!你這毒婦竟也來(lái)了陕壹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤树埠,失蹤者是張志新(化名)和其女友劉穎糠馆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體怎憋,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡又碌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绊袋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毕匀。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖癌别,靈堂內(nèi)的尸體忽然破棺而出皂岔,到底是詐尸還是另有隱情,我是刑警寧澤展姐,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布躁垛,位于F島的核電站,受9級(jí)特大地震影響圾笨,放射性物質(zhì)發(fā)生泄漏教馆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一擂达、第九天 我趴在偏房一處隱蔽的房頂上張望土铺。 院中可真熱鬧,春花似錦谍婉、人聲如沸舒憾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)镀迂。三九已至,卻和暖如春唤蔗,著一層夾襖步出監(jiān)牢的瞬間探遵,已是汗流浹背窟赏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箱季,地道東北人涯穷。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像藏雏,于是被迫代替她去往敵國(guó)和親拷况。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

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

  • [TOC] Golang的反射reflect深入理解和示例 【記錄于2018年2月】 編程語(yǔ)言中反射的概念 在計(jì)算...
    AllenWu閱讀 862評(píng)論 1 14
  • 編程語(yǔ)言中反射的概念 在計(jì)算機(jī)科學(xué)領(lǐng)域掘殴,反射是指一類應(yīng)用赚瘦,它們能夠自描述和自控制。也就是說(shuō)奏寨,這類應(yīng)用通過(guò)采用某種機(jī)...
    豆瓣奶茶閱讀 12,632評(píng)論 0 31
  • 首先巴拉巴拉一下golang反射機(jī)制的三個(gè)定律 1.反射可以從接口類型到反射類型對(duì)象 2.反射可以從反射類型對(duì)象到...
    吃貓的魚(yú)0閱讀 2,901評(píng)論 0 1
  • 寫在前面 反射機(jī)制是一個(gè)很重要的內(nèi)容起意,當(dāng)我們寫框架的時(shí)候,要想要松耦合病瞳,高復(fù)用揽咕,那么就有很多地方都需要用到反射,可...
    鬧鬧吃魚(yú)閱讀 765評(píng)論 0 2
  • Golang的反射reflect深入理解和示例 編程語(yǔ)言中反射的概念 在計(jì)算機(jī)科學(xué)領(lǐng)域套菜,反射是指一類應(yīng)用亲善,它們能夠...
    陳臥蟲(chóng)閱讀 413評(píng)論 0 0