golang 如何用反射reflect操作結(jié)構(gòu)體
背景
需要遍歷結(jié)構(gòu)體的所有field
對(duì)于exported的field, 動(dòng)態(tài)set這個(gè)field的value
對(duì)于unexported的field, 通過(guò)強(qiáng)行取址的方法來(lái)獲取該值(tricky?)
思路
下面的代碼實(shí)現(xiàn)了從一個(gè)strct ptr對(duì)一個(gè)包外結(jié)構(gòu)體進(jìn)行取值的操作,這種場(chǎng)合在筆者需要用到反射的場(chǎng)合中出現(xiàn)比較多
simpleStrtuctField 函數(shù)接受一個(gè)結(jié)構(gòu)體指針,因?yàn)樽詈笙M淖兤渲?,所以傳參必須是指針。然后解引用?/p>
接下來(lái)遍歷結(jié)構(gòu)體的每個(gè)field, exported字段是CanInterface的,對(duì)于unexported字段,需要強(qiáng)行取址來(lái)獲取其值
model.go
package model type Person struct { Name string age int } func NewPerson(name string, age int) *Person { return &Person{ Name: name, age: age, } }
main.go
package main import ( "github.com/miaomiao3/log" "../model" "reflect" "unsafe" ) func main() { person := model.NewPerson("haha", 12) log.Debug("before:%+v", person) simpleStrtuctField(person) simpleStrtuctField(person) log.Debug("after:%+v", person) } // get unexported field func simpleStrtuctField(v interface{}) { dataType := reflect.TypeOf(v) dataValue := reflect.ValueOf(v) if dataType.Kind() == reflect.Ptr { if dataValue.IsNil() { panic("nil ptr") } // 如果是指針,則要判斷一下是否為struct originType := reflect.ValueOf(v).Elem().Type() if originType.Kind() != reflect.Struct { return } // 解引用 dataValue = dataValue.Elem() dataType = dataType.Elem() } else { panic("non ptr") } num := dataType.NumField() for i := 0; i < num; i++ { field := dataType.Field(i) fieldName := field.Name fieldValue := dataValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } if fieldValue.CanInterface() { log.Debug("exported fieldName:%v value:%v", fieldName, fieldValue.Interface()) if fieldValue.CanSet() && fieldValue.Kind() == reflect.String { oldValue := fieldValue.Interface().(string) fieldValue.SetString(oldValue + " auto append") } } else { // 強(qiáng)行取址 forceValue := reflect.NewAt(fieldValue.Type(), unsafe.Pointer(fieldValue.UnsafeAddr())).Elem() log.Debug("unexported fieldName:%v value:%v", fieldName, forceValue.Interface()) } } }
output:
2019/06/02 17:15:31.64 [D] before:&{Name:haha age:12}
2019/06/02 17:15:31.64 [D] exported fieldName:Name value:haha
2019/06/02 17:15:31.64 [D] unexported fieldName:age value:12
2019/06/02 17:15:31.64 [D] after:&{Name:haha auto append age:12}
可以看到,Name字段被反射改變了,age的值也已經(jīng)獲取到
補(bǔ)充:go語(yǔ)言通過(guò)反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對(duì)應(yīng)方法
看代碼吧~
package main import ( "fmt" "reflect" "testing" ) type Call struct { Num1 int Num2 int } func (call Call) GetSub(name string){ fmt.Printf("%v 完成了減法運(yùn)算,%v - %v = %v \n", name, call.Num1, call.Num2, call.Num1 - call.Num2) } func (call *Call) GetSum(name string){ fmt.Printf("%v 完成了加法運(yùn)算,%v + %v = %v \n", name, call.Num1, call.Num2, call.Num1 + call.Num2) } func TestReflect(t *testing.T) { var ( call *Call rValues []reflect.Value rValues2 []reflect.Value ) ptrType := reflect.TypeOf(call) //獲取call的指針的reflect.Type trueType := ptrType.Elem() //獲取type的真實(shí)類型 ptrValue := reflect.New(trueType) //返回對(duì)象的指針對(duì)應(yīng)的reflect.Value call = ptrValue.Interface().(*Call) trueValue := ptrValue.Elem() //獲取真實(shí)的結(jié)構(gòu)體類型 trueValue.FieldByName("Num1").SetInt(123)//設(shè)置對(duì)象屬性,注意這個(gè)一定要是真實(shí)的結(jié)構(gòu)類型的reflect.Value才能調(diào)用,指針類型reflect.Value的會(huì)報(bào)錯(cuò) //ptrValue.FieldByName("Num2").SetInt(23) trueValue.FieldByName("Num2").SetInt(23) //rValues = make([]reflect.Value, 0) rValues = append(rValues, reflect.ValueOf("xiaopeng"))//調(diào)用對(duì)應(yīng)的方法 fmt.Println(rValues) trueValue.MethodByName("GetSub").Call(rValues) /* fixme 在反射中,指針的方法不可以給實(shí)際類型調(diào)用,實(shí)際類型的方法可以給指針類型調(diào)用,因?yàn)間o語(yǔ)言對(duì)這種操作做了封裝 所以下面一句是沒(méi)問(wèn)題的 下下一句會(huì)運(yùn)行時(shí)報(bào)錯(cuò) */ //ptrValue.MethodByName("GetSub").Call(rValues) //trueValue.MethodByName("GetSum").Call(append(rValues2, reflect.ValueOf("hiram"))) ptrValue.MethodByName("GetSum").Call(append(rValues2, reflect.ValueOf("hiram"))) fmt.Println(call) /* fixme 在實(shí)際使用中 指針和實(shí)體都能相互轉(zhuǎn)換,不會(huì)影響調(diào)用 但是指針的方法在方法體內(nèi)的操作會(huì)影響到結(jié)構(gòu)體本身屬性 而實(shí)體的方法不會(huì),因?yàn)間o對(duì)于結(jié)構(gòu)體、數(shù)組、基本類型都是值傳遞 */ call.GetSub("aaa") (*call).GetSub("bbb") call.GetSum("ccc") (*call).GetSum("ddd") }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
如何使用?Go?和?Excelize?構(gòu)建電子表格
這篇文章主要介紹了如何使用Go和Excelize構(gòu)建電子表格,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09Go語(yǔ)言基礎(chǔ)switch條件語(yǔ)句基本用法及示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)switch條件語(yǔ)句基本用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11golang 監(jiān)聽(tīng)服務(wù)的信號(hào),實(shí)現(xiàn)平滑啟動(dòng),linux信號(hào)說(shuō)明詳解
這篇文章主要介紹了golang 監(jiān)聽(tīng)服務(wù)的信號(hào),實(shí)現(xiàn)平滑啟動(dòng),linux信號(hào)說(shuō)明詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05Golang當(dāng)中的定時(shí)器實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于Golang當(dāng)中定時(shí)器的相關(guān)資料,定時(shí)器的實(shí)現(xiàn)大家應(yīng)該都遇到過(guò),最近在學(xué)習(xí)golang,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07golang通過(guò)遞歸遍歷生成樹狀結(jié)構(gòu)的操作
這篇文章主要介紹了golang通過(guò)遞歸遍歷生成樹狀結(jié)構(gòu)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Go語(yǔ)言學(xué)習(xí)之JSON編碼解析與使用
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中JSON編碼的解析與使用已經(jīng)JSON與Map、結(jié)構(gòu)體的互相轉(zhuǎn)化,文中的示例代碼講解詳細(xì),需要的可以參考一下2023-02-02Golang使用切片實(shí)現(xiàn)單鏈表的示例代碼
單鏈表(Single?Linked?List)是鏈表數(shù)據(jù)結(jié)構(gòu)的一種實(shí)現(xiàn)方式,它包含一系列節(jié)點(diǎn)(Node),每個(gè)節(jié)點(diǎn)都包含一個(gè)數(shù)據(jù)域和一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針,本文給大家介紹了Golang使用切片實(shí)現(xiàn)單鏈表的操作,需要的朋友可以參考下2024-05-05