Golang反射模塊reflect使用方式示例詳解
Golang的反射功能,在很多場(chǎng)景都會(huì)用到,最基礎(chǔ)的莫過(guò)于rpc、orm跟json的編解碼,更復(fù)雜的可能會(huì)到做另外一門語(yǔ)言的虛擬機(jī)。通過(guò)反射模塊,我們可以在編程語(yǔ)言的runtime運(yùn)行時(shí)期間去訪問(wèn)內(nèi)部產(chǎn)生對(duì)象的信息。了解反射模塊的實(shí)現(xiàn),對(duì)我們了解Golang對(duì)象機(jī)制本身,也是莫大的幫助。
今天,恰逢陽(yáng)康+新年,就決定來(lái)探究一下Golang的反射模塊——reflect。
從最基礎(chǔ)的開始,reflect模塊,以獲取整數(shù)對(duì)象的類型信息為例,我們可以這么用:
func TestReflect_Integer(t *testing.T) {
i := 1
v := reflect.ValueOf(i)
vKind := v.Kind()
vType := v.Type()
t.Logf("i kind: %+v\n", vKind)
t.Logf("i type: %+v\n", vType)
itf := v.Interface()
j, ok := itf.(int)
t.Logf("j val: %+v\n", j)
if !ok || j != i {
t.Fatalf("i != j")
}
}reflect.ValueOf的入?yún)⑹?code>interface{}類型,新版本也叫做any,得到的返回值是reflect.Value類型的對(duì)象,可以認(rèn)為是對(duì)原對(duì)象的一個(gè)描述性質(zhì)的對(duì)象(元對(duì)象,233)。reflect.Value包含幾個(gè)成員:
typ:原對(duì)象類型的元信息ptr:指向原對(duì)象值的原生指針flag:原對(duì)象類型的正整數(shù)標(biāo)識(shí)
當(dāng)調(diào)用Kind跟Type接口,四舍五入就是獲取了reflect.Value對(duì)象的flag跟typ成員實(shí)例了。
要把reflect.Value轉(zhuǎn)換回原對(duì)象,首先需要通過(guò)Interface方法轉(zhuǎn)化成interface{}類型的對(duì)象,再通過(guò).(int)強(qiáng)轉(zhuǎn)邏輯去轉(zhuǎn)化成原對(duì)象。但這里需要注意下,如果真需要用到reflect反射功能且涉及到一些看似要“強(qiáng)轉(zhuǎn)”的場(chǎng)景,可能是沒(méi)有必要真的在代碼中強(qiáng)轉(zhuǎn)回特定類型對(duì)象的。好比rpc的調(diào)用,實(shí)質(zhì)是在聲明接口方法的基礎(chǔ)上,把接口方法變成reflect.Value對(duì)象,再用Func.Call方法做函數(shù)調(diào)用。這里,也給個(gè)example:
假設(shè)我們定義User結(jié)構(gòu)體,內(nèi)部嵌套UserInfo結(jié)構(gòu)體,均有json標(biāo)注,然后定義了ToString跟Response兩個(gè)方法,大概長(zhǎng)這樣:
type UserInfo struct {
Desc string `json:"desc"`
City string `json:"city" desc:"the city of user"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Info *UserInfo `json:"info"`
}
func (u *User) ToString() string {
return fmt.Sprintf("[%d]%s", u.ID, u.Name)
}
func (u *User) Response(from string, msg string) string {
return fmt.Sprintf("User %s received msg %s from <%s>", u.ToString(), msg, from)
}那么比如Response方法,實(shí)際上也能夠這樣調(diào)用:
func TestReflect_Method(t *testing.T) {
u := &User{1, "jack", nil}
uPtr := reflect.ValueOf(u)
// MethodByName:獲取特定名字的Method
meth, ok := uPtr.Type().MethodByName("Response")
if !ok {
t.Fatalf("no method named Response")
}
t.Logf("meth Response: %+v\n", meth)
methType := meth.Type
// 入?yún)?個(gè):User實(shí)例、from、msg
if methType.NumIn() != 3 {
t.Fatalf("invalid NumIn %d, expected %d", methType.NumIn(), 3)
}
// 返回值1個(gè):response string
if methType.NumOut() != 1 {
t.Fatalf("invalid NumOut %d, expected %d", methType.NumOut(), 1)
}
// 通過(guò)Func.Call得到返回值list
from, msg := reflect.ValueOf("client"), reflect.ValueOf("ping")
rets := meth.Func.Call([]reflect.Value{uPtr, from, msg})
if len(rets) != 1 {
t.Fatalf("invalid num rets %d, expected %d", len(rets), 1)
}
// 返回1個(gè)string對(duì)象
respVal := rets[0]
if respVal.Type() != reflect.TypeOf("") {
t.Fatalf("invalid ret type %v, expected %s", respVal.Type(), "STRING")
}
resp, ok := respVal.Interface().(string)
if !ok {
t.Fatalf("ret value cannot be converted to string")
}
t.Logf("resp: %s\n", resp)
}通過(guò)MethodByName方法,可以定位到一個(gè)對(duì)象下的某個(gè)名字的方法實(shí)例,通過(guò)對(duì)方法實(shí)例調(diào)用Func.Call,就能實(shí)際實(shí)現(xiàn)對(duì)方法的調(diào)用,得到返回值列表。
涉及到指針對(duì)象的反射值,可以通過(guò)reflect.Indirect(反射值)或者反射值.Elem()的方式,獲取到指針指向?qū)嵗姆瓷渲怠?code>example代碼如下,因?yàn)樯厦嫖覀兌xToString和Response方法綁定的是指針對(duì)象,在這樣的條件下,指針指向?qū)嵗姆瓷渲稻湍貌坏?code>ToString方法了,打印便知:
func TestReflect_Pointer(t *testing.T) {
u := &User{1, "jack", nil}
vPtr := reflect.ValueOf(u)
vPtrKind := vPtr.Kind()
vPtrType := vPtr.Type()
t.Logf("ptr kind: %+v\n", vPtrKind)
t.Logf("ptr type: %+v\n", vPtrType)
meth, ok := vPtrType.MethodByName("ToString")
t.Logf("ptr meth ToString: %+v (%+v)\n", meth, ok)
vVal := reflect.Indirect(vPtr)
// vVal := vPtr.Elem()
vValKind := vVal.Kind()
vValType := vVal.Type()
t.Logf("val kind: %+v\n", vValKind)
t.Logf("val type: %+v\n", vValType)
meth, ok = vValType.MethodByName("ToString")
t.Logf("val meth ToString: %+v (%+v)\n", meth, ok)
}再進(jìn)一步來(lái)看,對(duì)于slice、map這類對(duì)象,也可以通過(guò)reflect.Value內(nèi)置的一些方法,訪問(wèn)到內(nèi)部的對(duì)象。假設(shè)我們要實(shí)現(xiàn)slice跟map的復(fù)制操作,用純反射的方式也可以實(shí)現(xiàn):
func TestReflect_CopySliceAndMap(t *testing.T) {
mp := map[string]int{
"jack": 1,
"tom": 2,
}
sl := []int{1, 1, 2, 3, 5, 8}
vals := []reflect.Value{reflect.ValueOf(mp), reflect.ValueOf(sl)}
var copyVals []reflect.Value
for _, val := range vals {
var copyVal reflect.Value
switch val.Kind() {
case reflect.Map:
// MakeMap:創(chuàng)建map實(shí)例
copyVal = reflect.MakeMap(val.Type())
// MapRange:獲取map對(duì)象的Iterator
iter := val.MapRange()
for iter.Next() {
copyVal.SetMapIndex(iter.Key(), iter.Value())
}
case reflect.Slice:
// AppendSlice:在一個(gè)slice的基礎(chǔ)上extend另一個(gè)slice
copyVal = reflect.AppendSlice(
reflect.MakeSlice(val.Type(), 0, val.Len()),
val)
}
copyVals = append(copyVals, copyVal)
}
// 通過(guò)DeepEqual方法,可以做值的相等性比較
for _, val := range copyVals {
switch val.Kind() {
case reflect.Map:
if val.Len() != len(mp) {
t.Fatalf("invalid map length %d, expected %d", val.Len(), len(mp))
}
copyVal, ok := val.Interface().(map[string]int)
if !ok {
t.Fatalf("map convert failed")
}
t.Logf("copied map: %+v", copyVal)
for k, v := range mp {
copyV, ok := copyVal[k]
if !ok || !reflect.DeepEqual(v, copyV) {
t.Fatalf("copy value of key %s failed, expected %d, actual %d", k, v, copyV)
}
}
case reflect.Slice:
if val.Len() != len(sl) {
t.Fatalf("invalid slice length %d, expected %d", val.Len(), len(sl))
}
copyVal, ok := val.Interface().([]int)
if !ok {
t.Fatalf("slice convert failed")
}
t.Logf("copied slice: %+v", copyVal)
if !reflect.DeepEqual(copyVal, sl) {
t.Fatalf("slice not equal")
}
}
}
}最后,也看一下reflect作用到struct定義的一些使用方法,這里就需要看reflect.Value.Type()返回的Type實(shí)例有什么功能了。對(duì)于Type實(shí)例來(lái)講,我們可以遍歷所有結(jié)構(gòu)體字段定義,甚至是訪問(wèn)標(biāo)注信息,比如json、orm的編解碼,就極度依賴這些字段的標(biāo)注信息。這里的實(shí)現(xiàn),也給個(gè)example:
func TestReflect_Struct(t *testing.T) {
st := reflect.ValueOf(&User{
ID: 9,
Name: "Ronaldo",
Info: &UserInfo{
Desc: "SC",
City: "Madrid",
},
}).Elem().Type()
// 多少個(gè)字段
numField := st.NumField()
t.Logf("num fields: %d", numField)
// 一個(gè)個(gè)字段遍歷,輸出字段名字、數(shù)據(jù)類型、json標(biāo)注
for i := 0; i < numField; i++ {
field := st.Field(i)
t.Logf("field %d -> name: %s, type: %v, json: %s",
i+1,
field.Name,
field.Type,
field.Tag.Get("json"))
}
// 嵌套的字段,用FieldByIndex可以定位到
cityField := st.FieldByIndex([]int{2, 1})
// 按照上面UserInfo.City的定義,拿取desc標(biāo)注信息
cityFieldDesc, ok := cityField.Tag.Lookup("desc")
if !ok {
t.Fatalf("cannot find city field desc")
}
t.Logf("CityField -> name: %s, type: %v, desc: %s",
cityField.Name,
cityField.Type,
cityFieldDesc)
}
可以看到,我們可以很方便拿到結(jié)構(gòu)體的定義信息,更加深層嵌套的定義信息也都能拿到。可以說(shuō),太靈活了!
到此這篇關(guān)于Golang反射模塊reflect使用方式探索的文章就介紹到這了,更多相關(guān)Golang反射模塊reflect內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Go語(yǔ)言實(shí)現(xiàn)選擇排序算法及優(yōu)化
選擇排序是一種簡(jiǎn)單的比較排序算法.這篇文章將利用Go語(yǔ)言實(shí)現(xiàn)冒泡排序算法,文中的示例代碼講解詳細(xì),對(duì)學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下2022-12-12
詳解Go語(yǔ)言如何實(shí)現(xiàn)一個(gè)最簡(jiǎn)化的協(xié)程池
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言如何實(shí)現(xiàn)一個(gè)最簡(jiǎn)化的協(xié)程池,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,有需要的小伙伴可以了解一下2023-10-10
Go 微服務(wù)開發(fā)框架DMicro設(shè)計(jì)思路詳解
這篇文章主要為大家介紹了Go 微服務(wù)開發(fā)框架DMicro設(shè)計(jì)思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Go語(yǔ)言如何使用分布式鎖解決并發(fā)問(wèn)題
這篇文章主要為大家詳細(xì)介紹了Go 語(yǔ)言生態(tài)中基于 Redis 實(shí)現(xiàn)的分布式鎖庫(kù) redsync,并探討其使用方法和實(shí)現(xiàn)原理,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03
Golang設(shè)計(jì)模式之生成器模式講解和代碼示例
生成器是一種創(chuàng)建型設(shè)計(jì)模式,使你能夠分步驟創(chuàng)建復(fù)雜對(duì)象,與其他創(chuàng)建型模式不同,生成器不要求產(chǎn)品擁有通用接口,這使得用相同的創(chuàng)建過(guò)程生成不同的產(chǎn)品成為可能,本文就通過(guò)代碼示例為大家詳細(xì)介紹Golang生成器模式,感興趣的同學(xué)可以參考下2023-06-06
對(duì)Golang中的runtime.Caller使用說(shuō)明
這篇文章主要介紹了對(duì)Golang中的runtime.Caller使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
淺析Go語(yǔ)言編程當(dāng)中映射和方法的基本使用
這篇文章主要介紹了淺析Go語(yǔ)言編程當(dāng)中映射和方法的基本使用,是golang入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10

