Golang反射-上篇
目錄
1、反射的定義
2、反射的基礎(chǔ)數(shù)據(jù)類型
3、Type
4、Value
5、反射三大定律
5.1 反射第一定律
5.2 反射第二定律
5.3 反射第三定律
6、反射常用的 API
6.1 獲取 type 類型
6.2 獲取 Field 信息
6.3 獲取 method 信息
6.4 獲取函數(shù)信息
6.5 賦值和轉(zhuǎn)換關(guān)系
6.6 是否實(shí)現(xiàn)接口
6.7 value 和其他類型的互換
6.8 value 判斷空值的三種情況

1、反射的定義
It’s a great source of confusion ~ (引用自官方博客)
反射是指在運(yùn)行時(shí)動(dòng)態(tài)的訪問和修改任意類型對(duì)象的結(jié)構(gòu)和成員,在go語言中提供reflect包提供反射的功能,每一個(gè)變量都有兩個(gè)屬性:類型Type和值Value
反射能夠自描述自控制
例如python的反射:根據(jù)字符串執(zhí)行函數(shù),根據(jù)字符串導(dǎo)入包
go是靜態(tài)語言,反射就是go提供的一種機(jī)制,在編譯時(shí)不知道類型的情況下可以做如下的事情
更新變量 運(yùn)行時(shí)查看值 調(diào)用方法 對(duì)他們的布局進(jìn)行操作
使用反射的兩個(gè)經(jīng)典場景
你編寫的這個(gè)函數(shù),還不知道傳給你的類型具體是什么,可能是還沒約定好,也可能是傳入的類型很多
希望通過用戶的輸入來決定調(diào)用哪個(gè)函數(shù)(根據(jù)字符串調(diào)用方法),動(dòng)態(tài)執(zhí)行函數(shù)
2、反射的基礎(chǔ)數(shù)據(jù)類型

reflect.Type是一個(gè)接口類型,用于獲取變量類型相關(guān)的信息,可通過reflect.TypeOf函數(shù)獲取某個(gè)變量的類型信息
源碼go/src/reflect/type.go
type?Type?interface?{
?Align()?int
?FieldAlign()?int
?Method(int)?Method??//?第?i?個(gè)方法
?MethodByName(string)?(Method,?bool)??//?根據(jù)名稱獲取方法
?NumMethod()?int??//?方法的個(gè)數(shù)
?Name()?string??//?獲取結(jié)構(gòu)體名稱
?PkgPath()?string??//?包路徑
?Size()?uintptr??//?占用內(nèi)存的大小
?String()?string??//?獲取字符串表述
?Kind()?Kind?//?數(shù)據(jù)類型
?Implements(u?Type)?bool??//?判斷是否實(shí)現(xiàn)了某接口
?AssignableTo(u?Type)?bool??//?能否賦給另外一種類型
?ConvertibleTo(u?Type)?bool??//?能否轉(zhuǎn)換為另外一種類型
?Comparable()?bool
?Bits()?int
?ChanDir()?ChanDir
?IsVariadic()?bool
?Elem()?Type??//?解析指針(指針類型轉(zhuǎn)為普通類型)
?Field(i?int)?StructField??//?第i個(gè)成員
?FieldByIndex(index?[]int)?StructField??//?根據(jù)index路徑獲取嵌套成員
?FieldByName(name?string)?(StructField,?bool)??//?根據(jù)名稱獲取成員
?FieldByNameFunc(match?func(string)?bool)?(StructField,?bool)
?In(i?int)?Type
?Key()?Type
?Len()?int??//?容器的長度
?NumField()?int
?NumIn()?int??//?輸出參數(shù)的個(gè)數(shù)
?NumOut()?int??//?返回參數(shù)的個(gè)數(shù)
?Out(i?int)?Type
?common()?*rtype
?uncommon()?*uncommonType
}
4、Value
reflect.Value是一個(gè)結(jié)構(gòu)體類型,用于獲取變量值的信息,可通過reflect.ValueOf函數(shù)獲取修改原始數(shù)據(jù)類型(某個(gè)變量)的值信息
源碼go/src/reflect/value.go
type?Value?struct?{
???//?代表的數(shù)據(jù)類型
?typ?*rtype
?//?指向原始數(shù)據(jù)的指針
?ptr?unsafe.Pointer
}
5、反射三大定律
interface類型有個(gè)value,type對(duì),而反射就是檢查interface的這個(gè)value, type對(duì)的
具體一點(diǎn)說就是Go提供一組方法提取interface的value,提供另一組方法提取interface的type
reflect.Type提供一組接口處理interface的類型,即value,type中的typereflect.Value提供一組接口處理interface的值,即value,type中的value
5.1 反射第一定律
反射第一定律:反射可以將interface類型變量轉(zhuǎn)換成反射對(duì)象
如何通過反射獲取一個(gè)變量的值和類型
package?main
import?(
????"fmt"
????"reflect"
)
func?main()?{
????var?x?float64?=?3.4
????t?:=?reflect.TypeOf(x)??//t?is?reflext.Type
????fmt.Println("type:",?t)
????fmt.Println("kind?is?float64:",?v.Kind()?==?reflect.Float64)
????v?:=?reflect.ValueOf(x)?//v?is?reflext.Value
????fmt.Println("value:",?v)
}
程序輸出
type:?float64
kind?is?float64:?true
value:?3.4
反射是針對(duì)interface類型變量的,其中TypeOf()和ValueOf()接受的參數(shù)都是interface{}類型的,也即x值是被轉(zhuǎn)成了interface傳入的
5.2 反射第二定律
反射第二定律:反射可以將反射對(duì)象還原成interface對(duì)象
之所以叫’反射’,反射對(duì)象與interface對(duì)象是可以互相轉(zhuǎn)化的
package?main
import?(
????"fmt"
????"reflect"
)
func?main()?{
????var?x?float64?=?3.4
????v?:=?reflect.ValueOf(x)?//v?is?reflext.Value
????var?y?float64?=?v.Interface().(float64)
????fmt.Println("value:",?y)
}
對(duì)象x轉(zhuǎn)換成反射對(duì)象v,v又通過Interface()接口轉(zhuǎn)換成interface對(duì)象,interface對(duì)象通過.(float64)類型斷言獲取float64類型的值
5.3 反射第三定律
反射第三定律:反射對(duì)象可修改,value值必須是可設(shè)置的
通過反射可以將interface類型變量轉(zhuǎn)換成反射對(duì)象,可以使用該反射對(duì)象設(shè)置其持有的值
package?main
import?(
????"reflect"
)
func?main()?{
????var?x?float64?=?3.4
????v?:=?reflect.ValueOf(x)
????v.SetFloat(7.1)?//?Error:?will?panic.
}
通過反射對(duì)象 v 設(shè)置新值,會(huì)出現(xiàn)panic
panic:?reflect:?reflect.Value.SetFloat?using?unaddressable?value
錯(cuò)誤原因即是v是不可修改的。
反射對(duì)象是否可修改取決于其所存儲(chǔ)的值,回想一下函數(shù)傳參時(shí)是傳值還是傳址就不難理解上例中為何失敗了。
上面例子傳入reflect.ValueOf()函數(shù)的其實(shí)是x的值,而非x本身。即通過v修改其值是無法影響x的,也即是無效的修改,所以golang會(huì)報(bào)錯(cuò)
想到此處,即可明白,如果構(gòu)建v時(shí)使用x的地址就可實(shí)現(xiàn)修改了,但此時(shí)v代表的是指針地址,我們要設(shè)置的是指針?biāo)赶虻膬?nèi)容,也即我們想要修改的是*v。通過v修改x的值?
reflect.Value提供了Elem()方法,可以獲得指針向指向的value
package?main
import?(
"reflect"
????"fmt"
)
func?main()?{
????var?x?float64?=?3.4
????v?:=?reflect.ValueOf(&x)
????v.Elem().SetFloat(7.1)
????fmt.Println("x?:",?v.Elem().Interface())
}
輸出為:
x?:?7.1
6、反射常用的 API
6.1 獲取 type 類型
typeUser?:=?reflect.TypeOf(&User{})?//通過TypeOf()得到Type類型
fmt.Println(typeUser)??????????????????????//*User
fmt.Println(typeUser.Elem())???????????????//User
fmt.Println(typeUser.Name())???????????????//空字符串
fmt.Println(typeUser.Elem().Name())????????//User,不帶包名的類名稱
fmt.Println(typeUser.Kind())???????????????//ptr
fmt.Println(typeUser.Elem().Kind())????????//struct
fmt.Println(typeUser.Kind()?==?reflect.Ptr)
fmt.Println(typeUser.Elem().Kind()?==?reflect.Struct)
6.2 獲取 Field 信息
typeUser?:=?reflect.TypeOf(User{})?//需要用struct的Type,不能用指針的Type
fieldNum?:=?typeUser.NumField()???????????//成員變量的個(gè)數(shù)
for?i?:=?0;?i??field?:=?typeUser.Field(i)
?fmt.Printf("%d?%s?offset?%d?anonymous?%t?type?%s?exported?%t?json?tag?%s\n",?i,
??field.Name,????????????//變量名稱
??field.Offset,??????????//相對(duì)于結(jié)構(gòu)體首地址的內(nèi)存偏移量,string類型會(huì)占據(jù)16個(gè)字節(jié)
??field.Anonymous,???????//是否為匿名成員
??field.Type,????????????//數(shù)據(jù)類型,reflect.Type類型
??field.IsExported(),????//包外是否可見(即是否以大寫字母開頭)
??field.Tag.Get("json"))?//獲取成員變量后面``里面定義的tag
//可以通過FieldByName獲取Field
if?nameField,?ok?:=?typeUser.FieldByName("Name");?ok?{
?fmt.Printf("Name?is?exported?%t\n",?nameField.IsExported())
}
//也可以根據(jù)FieldByIndex獲取Field
thirdField?:=?typeUser.FieldByIndex([]int{2})?//參數(shù)是個(gè)slice,因?yàn)橛衧truct嵌套的情況
fmt.Printf("third?field?name?%s\n",?thirdField.Name)
}
6.3 獲取 method 信息
typeUser?:=?reflect.TypeOf(common.User{})
methodNum?:=?typeUser.NumMethod()?//成員方法的個(gè)數(shù)。接收者為指針的方法【不】包含在內(nèi)
for?i?:=?0;?i??method?:=?typeUser.Method(i)
?fmt.Println(method.Type)??//?會(huì)輸出函數(shù)完整的簽名,其中輸入?yún)?shù)會(huì)將結(jié)構(gòu)體本身也作為輸入?yún)?shù),因此參數(shù)個(gè)數(shù)多一個(gè)
?fmt.Printf("method?name:%s?,type:%s,?exported:%t\n",?method.Name,?method.Type,?method.IsExported())
}
fmt.Println()
if?method,?ok?:=?typeUser.MethodByName("Examine2");?ok?{??//?根據(jù)方法名獲取
?fmt.Printf("method?name:%s?,type:%s,?exported:%t\n",?method.Name,?method.Type,?method.IsExported())
}
typeUser2?:=?reflect.TypeOf(&common.User{})
methodNum?=?typeUser2.NumMethod()?//成員方法的個(gè)數(shù)。接收者為指針或值的方法都包含在內(nèi),也就是說值實(shí)現(xiàn)的方法指針也實(shí)現(xiàn)了(反之不成立)
for?i?:=?0;?i??method?:=?typeUser2.Method(i)
?fmt.Printf("method?name:%s?,type:%s,?exported:%t\n",?method.Name,?method.Type,?method.IsExported())
}
6.4 獲取函數(shù)信息
typeFunc?:=?reflect.TypeOf(Add)?//獲取函數(shù)類型
fmt.Printf("is?function?type?%t\n",?typeFunc.Kind()?==?reflect.Func)
argInNum?:=?typeFunc.NumIn()???//輸入?yún)?shù)的個(gè)數(shù)
argOutNum?:=?typeFunc.NumOut()?//輸出參數(shù)的個(gè)數(shù)
for?i?:=?0;?i??argTyp?:=?typeFunc.In(i)
?fmt.Printf("第%d個(gè)輸入?yún)?shù)的類型%s\n",?i,?argTyp)
}
for?i?:=?0;?i??argTyp?:=?typeFunc.Out(i)
?fmt.Printf("第%d個(gè)輸出參數(shù)的類型%s\n",?i,?argTyp)
}
6.5 賦值和轉(zhuǎn)換關(guān)系
type1.AssignableTo(type2) // type1 代表的類型是否可以賦值給 type2 代表的類型 type1.ConvertibleTo(type2)) // type1 代表的類型是否可以轉(zhuǎn)換成 type2 代表的類型 java 的反射可以獲取繼承關(guān)系,而 go 語言不支持繼承,所以必須是相同的類型才能 AssignableTo 和 ConvertibleTo
示例
u?:=?reflect.TypeOf(User{})
t?:=?reflect.TypeOf(Student{})?//Student內(nèi)部嵌套了User
u2?:=?reflect.TypeOf(User{})
//false?false
fmt.Println(t.AssignableTo(u))??//t代表的類型是否可以賦值給u代表的類型
fmt.Println(t.ConvertibleTo(u))?//t代表的類型是否可以轉(zhuǎn)換成u代表的類型
//false?false
fmt.Println(u.AssignableTo(t))
fmt.Println(u.ConvertibleTo(t))
//true?true
fmt.Println(u.AssignableTo(u2))
fmt.Println(u.ConvertibleTo(u2))
6.6 是否實(shí)現(xiàn)接口
//通過reflect.TypeOf((*)(nil)).Elem()獲得接口類型。因?yàn)镻eople是個(gè)接口不能創(chuàng)建實(shí)例,所以把nil強(qiáng)制轉(zhuǎn)為*common.People類型
typeOfPeople?:=?reflect.TypeOf((*common.People)(nil)).Elem()??//?可以將nil理解成People指針的一個(gè)實(shí)例
fmt.Printf("typeOfPeople?kind?is?interface?%t\n",?typeOfPeople.Kind()?==?reflect.Interface)
t1?:=?reflect.TypeOf(common.User{})
t2?:=?reflect.TypeOf(&common.User{})
//User的值類型實(shí)現(xiàn)了接口,則指針類型也實(shí)現(xiàn)了接口;但反過來不行(把Think的接收者改為*User試試)
fmt.Printf("t1?implements?People?interface?%t\n",?t1.Implements(typeOfPeople))??//?false
fmt.Printf("t2?implements?People?interface?%t\n",?t2.Implements(typeOfPeople))??//?true
6.7 value 和其他類型的互換
//原始類型轉(zhuǎn)為Value
iValue?:=?reflect.ValueOf(1)
sValue?:=?reflect.ValueOf("hello")
userPtrValue?:=?reflect.ValueOf(&common.User{
?Id:?????7,
?Name:???"杰克遜",
?Weight:?65,
?Height:?1.68,
})
fmt.Println(iValue)???????//1
fmt.Println(sValue)???????//hello
fmt.Println(userPtrValue)?//&{7?杰克遜??65?1.68}
//Value轉(zhuǎn)為Type
iType?:=?iValue.Type()
sType?:=?sValue.Type()
userType?:=?userPtrValue.Type()
//在Type和相應(yīng)Value上調(diào)用Kind()結(jié)果一樣的
fmt.Println(iType.Kind()?==?reflect.Int,?iValue.Kind()?==?reflect.Int,?iType.Kind()?==?iValue.Kind())???????????????????//true?true
fmt.Println(sType.Kind()?==?reflect.String,?sValue.Kind()?==?reflect.String,?sType.Kind()?==?sValue.Kind())?????????????//true?true
fmt.Println(userType.Kind()?==?reflect.Ptr,?userPtrValue.Kind()?==?reflect.Ptr,?userType.Kind()?==?userPtrValue.Kind())?//true?true?true
//指針Value和非指針Value互相轉(zhuǎn)換
userValue?:=?userPtrValue.Elem()????????????????????//Elem()?指針Value轉(zhuǎn)為非指針Value
fmt.Println(userValue.Kind(),?userPtrValue.Kind())??//struct?ptr
userPtrValue3?:=?userValue.Addr()???????????????????//Addr()?非指針Value轉(zhuǎn)為指針Value
fmt.Println(userValue.Kind(),?userPtrValue3.Kind())?//struct?ptr
//轉(zhuǎn)為原始類型
//通過Interface()函數(shù)把Value轉(zhuǎn)為interface{},再從interface{}強(qiáng)制類型轉(zhuǎn)換,轉(zhuǎn)為原始數(shù)據(jù)類型
//或者在Value上直接調(diào)用Int()、String()等一步到位
fmt.Printf("origin?value?iValue?is?%d?%d\n",?iValue.Interface().(int),?iValue.Int())
fmt.Printf("origin?value?sValue?is?%s?%s\n",?sValue.Interface().(string),?sValue.String())
user?:=?userValue.Interface().(common.User)
fmt.Printf("id=%d?name=%s?weight=%.2f?height=%.2f\n",?user.Id,?user.Name,?user.Weight,?user.Height)
user2?:=?userPtrValue.Interface().(*common.User)
fmt.Printf("id=%d?name=%s?weight=%.2f?height=%.2f\n",?user2.Id,?user2.Name,?user2.Weight,?user2.Height)
6.8 value 判斷空值的三種情況
pointer、channel、func、interface、map、slice的預(yù)先聲明都是nil
var?i?interface{}?//接口沒有指向具體的值
v?:=?reflect.ValueOf(i)
fmt.Printf("v持有值?%t,?type?of?v?is?Invalid?%t\n",?v.IsValid(),?v.Kind()?==?reflect.Invalid)??//?false
var?user?*common.User?=?nil
v?=?reflect.ValueOf(user)?//Value指向一個(gè)nil
if?v.IsValid()?{
?fmt.Printf("v持有的值是nil?%t\n",?v.IsNil())?//調(diào)用IsNil()前先確保IsValid(),否則會(huì)panic??//?true
}
var?u?common.User?//只聲明,里面的值都是0值
v?=?reflect.ValueOf(u)
if?v.IsValid()?{
?fmt.Printf("v持有的值是對(duì)應(yīng)類型的0值?%t\n",?v.IsZero())?//調(diào)用IsZero()前先確保IsValid(),否則會(huì)panic??//?true
}
See you ~
參考資料
https://go.dev/blog/laws-of-reflection
歡迎進(jìn)群一起進(jìn)行技術(shù)交流
加群方式:公眾號(hào)消息私信“加群”或加我好友再加群均可
