【譯文】原文地址
關(guān)于Go反射這個(gè)主題,需要理解Go內(nèi)部關(guān)于結(jié)構(gòu)體骗炉、接口和類(lèi)型系統(tǒng)照宝,才能理解反射的底層工作機(jī)制。當(dāng)然句葵,您也使用反射厕鹃,而不需要深入理解這些細(xì)節(jié)。本文的目標(biāo)是向您介紹一些細(xì)節(jié)乍丈,使您能夠更深入地理解反射剂碴。但是這些不是嚴(yán)格要求的。這篇文章假設(shè)您對(duì)結(jié)構(gòu)體和接口有基本的了解轻专。你可以通過(guò)"Go by example"快速瀏覽下結(jié)構(gòu)體和接口忆矛,也可以深入學(xué)習(xí)下Go的結(jié)構(gòu)體和接口。
什么是反射
In computer science, reflection is the ability of a process to examine, introspect, and modify its own structure and behavior. — Wikipedia
維基百科上面解釋到请垛,在計(jì)算機(jī)科學(xué)中催训,反射是一種能夠?qū)Y(jié)構(gòu)體和行為(本人理解為函數(shù)或者方法)進(jìn)行檢查、內(nèi)省的過(guò)程宗收。
反射是程序運(yùn)行時(shí)的操作漫拭。它是一種元編程形式,但并不是所有的元編程都是反射混稽。
為什么反射對(duì)Go語(yǔ)言重要
反射在很多方面都起作用采驻。通過(guò)本文我將關(guān)注最明顯的一個(gè)作用审胚。Go作為一種靜態(tài)編程語(yǔ)言,你必須提前聲明所有類(lèi)型才可以使用礼旅。因此膳叨,你沒(méi)辦法處理事先不清楚的類(lèi)型,即使你需要對(duì)它進(jìn)行操作各淀、檢查你也不需要了解這些信息懒鉴。
一個(gè)典型的例子就是fmt包中的print函數(shù)。如果你想打印一個(gè)變量的類(lèi)型可以使用%T碎浇,fmt包不需要知道你自定義的Person結(jié)構(gòu)體。但是它還是能打印出Person接頭體內(nèi)容璃俗。
空接口
接口是一種定義了多個(gè)方法的類(lèi)型奴璃,實(shí)現(xiàn)了這些方法的結(jié)構(gòu)體就實(shí)現(xiàn)了該接口。這允許將接口作為一種類(lèi)型傳給方法使用城豁,您可以將實(shí)現(xiàn)了該接口的結(jié)構(gòu)體傳入方法苟穆。對(duì)于一個(gè)空接口,每個(gè)結(jié)構(gòu)體和每個(gè)基本類(lèi)型唱星,內(nèi)建實(shí)現(xiàn)了空接口雳旅。
因此,使用空接口作為類(lèi)型的函數(shù)參數(shù)间聊,可以接受任意類(lèi)型參數(shù)攒盈。
func main() {
x := 100
fmt.Println(x+1)
myPrint(x)
}
func myPrint(item interface{}) {
fmt.Println(item)
}
上面的代碼可以正常工作,第一個(gè)print將打印101哎榴,因?yàn)閷?duì)類(lèi)型int的變量x執(zhí)行了+1操作型豁,然后將x傳給Println,該函數(shù)也是接收一個(gè)空接口作為參數(shù)尚蝌,內(nèi)部是反射實(shí)現(xiàn)迎变。
但是Go還是一個(gè)靜態(tài)類(lèi)型語(yǔ)言,所以使用空接口將不允許您對(duì)變量進(jìn)行任何其他操作(除非使用類(lèi)型斷言或反射)飘言。
func main() {
x := 100
myPrint(x)
}
func myPrint(item interface{}) {
fmt.Println(item+1)
fmt.Println(item)
}
上面的代碼無(wú)法編譯通過(guò)衣形,在myPrint函數(shù)中,item是空接口類(lèi)型姿鸿,即使底層是整形谆吴,但是Go并不知道它,因此代碼會(huì)panic般妙。
類(lèi)型斷言
類(lèi)型斷言可以幫助你驗(yàn)證變量的實(shí)際類(lèi)型纪铺,如果它是您斷言的類(lèi)型,就會(huì)以這種類(lèi)型來(lái)獲取對(duì)應(yīng)值碟渺。
func main() {
var myVar interface{} = 10
v, ok := myVar.(int)
if (ok) {
fmt.Println(v)
}
}
上面的代碼會(huì)打印10鲜锚,因?yàn)槲覀兪褂胢yVar.(int)得到對(duì)應(yīng)的原始類(lèi)型突诬。斷言成功的話(huà),v將賦值為對(duì)應(yīng)類(lèi)型變量芜繁,ok將賦值為bool值旺隙。
關(guān)于類(lèi)型斷言的更多內(nèi)容,骏令,如果您感興趣的話(huà)可以瀏覽類(lèi)型斷言和類(lèi)型切換蔬捷。
類(lèi)型實(shí)現(xiàn)細(xì)節(jié)在哪里呢?
類(lèi)型斷言(以及代理榔袋,反射)是如何知道一個(gè)通用接口(空接口)的底層類(lèi)型的呢周拐。
要理解這點(diǎn),需要通過(guò)/src/sync/atomic/value.go直接看go實(shí)現(xiàn)凰兑。它實(shí)現(xiàn)了go中每個(gè)變量的基礎(chǔ)值妥粟。
// ifaceWords is interface{} internal representation.
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
空接口和go通過(guò)擴(kuò)展的每個(gè)值,在底層表示中包括兩個(gè)unsafe指針:typ和data吏够。
- typ保存當(dāng)前變量的類(lèi)型信息勾给,因此即使一個(gè)變量是空接口,實(shí)際的類(lèi)型信息在typ中是完整的锅知。
- data保存值本身播急,還有其他數(shù)據(jù)信息如kind值,這個(gè)不在本文討論范圍之內(nèi)售睹。重點(diǎn)是data保存類(lèi)型的值信息桩警。
類(lèi)型斷言缺少什么?(或者為何需要反射)
當(dāng)你知道要檢查的類(lèi)型時(shí)侣姆,斷言允許驗(yàn)證和使用接口的底層類(lèi)型值生真。在前面的例子中,我們專(zhuān)門(mén)為int使用斷言捺宗。因?yàn)槲覀兲崆爸榔漕?lèi)型柱蟀,以便斷言可以正常工作。即使我們用switch多個(gè)case來(lái)檢查蚜厉,我們?nèi)匀槐仨氈涝诰幾g時(shí)斷言的具體類(lèi)型长已。
在編譯時(shí)不知道具體的類(lèi)型情況下,就需要反射了昼牛∈跷停或者換句話(huà)說(shuō),如本文開(kāi)頭所述贰健,當(dāng)我們需要在運(yùn)行時(shí)檢查胞四。回想下fmt例子伶椿,fmt包并不知道結(jié)構(gòu)體類(lèi)型但仍然可以打印它的值和類(lèi)型(使用%T)辜伟。
反射
Reflect.Type和Reflect.Value是反射包提供的兩個(gè)基本且最終要的類(lèi)型氓侧。它們是Reflect包中定義的兩個(gè)結(jié)構(gòu)體,reflect包有操作接口變量的方法导狡,底層實(shí)現(xiàn)其實(shí)都是通過(guò)將接口的typ和data信息復(fù)制到這兩個(gè)結(jié)構(gòu)體上约巷。這樣通過(guò)這兩個(gè)結(jié)構(gòu)體對(duì)應(yīng)的方法即可處理接口了。
Reflect.TypeOf()和Reflect.ValueOf()是兩個(gè)可用的基本方法旱捧,分別返回Reflect.Type和Reflect.Value独郎,如下所示:
import (
"fmt"
"reflect"
)
func main() {
var myVar interface{} = 10
myType := reflect.TypeOf(myVar)
myValue := reflect.ValueOf(myVar)
fmt.Println(myType) // > int
fmt.Println(myValue) // > 10
}
為了更清楚的說(shuō)明:我們可以看一下自己定義的struct例子:
type Person struct {
name string
}
func main() {
var myPerson interface{} = Person{name: "Snir David"}
myType := reflect.TypeOf(myPerson)
myValue := reflect.ValueOf(myPerson)
fmt.Println(myType) // > main.Person
fmt.Println(myValue) // > {Snir David}
}
使用TypeOf和VauleOf返回底層類(lèi)型,和一個(gè)指向值的指針枚赡。需要注意的是ValueOf返回的的值類(lèi)型是Reflect.Value類(lèi)型的氓癌,并不是變量原始類(lèi)型。
例如贫橙,前面的例子中顽铸,我們不能取接口的值并對(duì)其做算數(shù)運(yùn)算,比如myValue + 1料皇。這是無(wú)法通過(guò)編譯的,因?yàn)镚o編譯器無(wú)法根據(jù)Reflect.Value類(lèi)型識(shí)別這個(gè)操作星压,但對(duì)原始類(lèi)型int是可識(shí)別的践剂。
Reflect.Kind
The kind is what the type is made of — a slice, a map, a pointer, a struct, an interface, a string, an array, a function, an int or some other primitive type.
理解Kind是有點(diǎn)棘手的,而且網(wǎng)上的一些介紹也會(huì)讓您感到困惑娜膘,因?yàn)榇蟛糠纸榻Bkind都是根據(jù)type理論逊脯,和討論Haskell之類(lèi)的語(yǔ)言實(shí)現(xiàn)。
關(guān)于Go你需要知道的是竣贪,每個(gè)變量都有一個(gè)Kind類(lèi)型军洼,是從type派生出來(lái)的。Kind可以理解為是類(lèi)型中的類(lèi)型演怎。
最容易說(shuō)清楚kind概念就是自己定義的結(jié)構(gòu)體匕争。讓我們回到前面創(chuàng)建Person結(jié)構(gòu)體的代碼。我們定義的myPerson是一個(gè)Person類(lèi)型爷耀。Person結(jié)構(gòu)體本身類(lèi)型甘桑,即Kind是struct。獲取kind類(lèi)型可以通過(guò)對(duì)Reflect.Type變量使用Kind()方法
例如上面的代碼可以修改為:
myKind := reflect.TypeOf(myPerson).Kind()
可以在如下鏈接查看所有的Kind值:
https://golang.org/pkg/reflect/#Kind
對(duì)一些例子歹叮,不像上面struct比較明顯跑杭,Kind看起來(lái)似乎有點(diǎn)重復(fù)。例如type是int64其Kind也是int64咆耿。無(wú)需強(qiáng)調(diào)的是德谅,我們了解Kind是因?yàn)樗兄谥赜每战涌谥担ㄗg者:這里似乎沒(méi)解釋清楚)。
將Reflect.Value轉(zhuǎn)換為原始類(lèi)型值
因此我們根據(jù)Reflect.ValueOf()函數(shù)可以對(duì)一個(gè)空接口類(lèi)型變量進(jìn)行分析萨螺,并得到一個(gè)類(lèi)型為Rreflect.Value值呕童。但是這個(gè)值并不正真有用主到,因?yàn)镚o類(lèi)型系統(tǒng)無(wú)法識(shí)別出它的原始類(lèi)型嘴高。
我們希望將該值轉(zhuǎn)換成其原始類(lèi)型。這個(gè)過(guò)程如下:
1冯键、使用Reflect.TypeOf或Reflect.Kind()識(shí)別出其原始類(lèi)型。
2庸汗、使用指向值的指針來(lái)獲取原始值(unsafe指針不在本文范圍內(nèi))
3惫确、對(duì)指針進(jìn)行類(lèi)型轉(zhuǎn)換
很幸運(yùn),reflect包已經(jīng)提供了處理所有的基本類(lèi)型轉(zhuǎn)換函數(shù)蚯舱。如下所示:
func main() {
var myVar interface{} = 10
reflectValue := reflect.ValueOf(myVar)
intValue := reflectValue.Int()
// Arithmetic will now work, as this is typed int
fmt.Println(intValue + 1) // > 11
}
所有的基本類(lèi)型都有轉(zhuǎn)換方法可用改化,Bool、Float枉昏、String等陈肛。
復(fù)雜類(lèi)型的探究
上面介紹了基本類(lèi)型,但是我們?nèi)绾翁幚斫Y(jié)構(gòu)體呢兄裂?
reflect包也提供了方法來(lái)查看結(jié)構(gòu)體內(nèi)部信息句旱。如下代碼所示:
type Person struct {
name string
age int
}
func investigateStruct(s interface{}) {
reflValue := reflect.ValueOf(s)
// Make sure we are handling with a struct here
if (reflValue.Kind() == reflect.Struct) {
fieldCount := reflValue.NumField()
fmt.Println("Num of fields: ", fieldCount)
for i := 0; i < fieldCount; i++ {
// Get individual field details
field := reflValue.Field(i)
fmt.Printf("type: %T, value: %v \n", field, field)
}
}
}
func main() {
var myVar interface{} = Person{name: "Snir", age: 27}
investigateStruct(myVar)
}
Output:
Num of fields: 2
type: reflect.Value, value: Snir
type: reflect.Value, value: 27
以上代碼查看了結(jié)構(gòu)體中包含的字段數(shù),以及每個(gè)字段類(lèi)型和值晰奖。