這篇文章主要介紹“Golang中的反射規(guī)則是什么”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強(qiáng),希望這篇“Golang中的反射規(guī)則是什么”文章能幫助大家解決問題。
創(chuàng)新互聯(lián)是專業(yè)的安多網(wǎng)站建設(shè)公司,安多接單;提供成都網(wǎng)站制作、網(wǎng)站設(shè)計,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行安多網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊,希望更多企業(yè)前來合作!
簡單來看,反射就是在程序運(yùn)行時期對程序本身進(jìn)行訪問和修改的能力,例如在程序運(yùn)行時,可以修改程序的字段名稱,字段值,還可以給程序提供接口訪問的信息等等
這是 Go 語言中提供的一種機(jī)制,我們可以在 Go 語言公共庫中可以看到很多關(guān)于 reflect 的使用位置
例如常用的 fmt 包,常用的json序列化和反序列化,自然前面我們說到的 gorm 庫自然也是使用了反射的
可是我們一般為什么要使用反射呢?
根據(jù)反射的能力,自然是因為我們提供的接口并不知道傳入的數(shù)據(jù)類型會是什么樣的, 只有當(dāng)程序運(yùn)行的時候才知道具體的數(shù)據(jù)類型
但是我們編碼的時候又期望去校驗程序運(yùn)行時傳入的類型會是什么樣的(例如 json 的序列化)并對其這種具體的數(shù)據(jù)進(jìn)行操作,這個時候,咱們就需要用到反射的能力了
所以對于使用到反射的地方,你都能看到 interface{}是不是就不奇怪了呢?
正是因為不確定傳入的數(shù)據(jù)類型會是什么樣的,所以才設(shè)計成 interface{}
首先關(guān)注反射的三個重要的定律,知道規(guī)則之后,我們按照規(guī)則玩就不會有什么問題,只有當(dāng)我們不清楚規(guī)則,總是觸發(fā)條款的時候,才會出現(xiàn)奇奇怪怪的問題
反射是可以將 接口類型的變量 轉(zhuǎn)換成 反射類型的對象
反射可以將 反射類型的對象 轉(zhuǎn)換成 接口類型的變量
我們在運(yùn)行時要去修改的 反射類型的對象 ,那么要求這個對象對應(yīng)的值是要可寫的
對于上述 3 個規(guī)則也是比較好理解,還記的之前我們說過的 unsafe 包里面的指針嗎?
都是將我們常用的數(shù)據(jù)類型,轉(zhuǎn)換成包(例如 unsafe包,或者 reflect 包)里面的指定數(shù)據(jù)類型,然后再按照包里面的規(guī)則進(jìn)行修改數(shù)據(jù)
相當(dāng)于,換個馬甲,就可以進(jìn)行不同的操作了
一般咱們先會基本的應(yīng)用,再去研究他的原理,研究他為什么可以這樣用,慢慢的才能理解的更加深刻
實際上此處說的 接口類型的變量 我們可以傳入任意數(shù)據(jù)類型的變量,例如 int, float, string ,map, slice, struct
等等
反射類型的對象 這里就可以理解成 reflect 反射包中的 reflect.Type
和 reflect.Value
對象,可以通過 reflect 包中提供的 TypeOf和 ValueOf函數(shù)得到
其中 reflect.Type
實際上是一個 interface ,他里面包含了各種接口需要進(jìn)行實現(xiàn),它里面提供了關(guān)于類型相關(guān)的信息
其中如下圖可以查看到 reflect.Type
的所有方法,其中
綠色的 是所有數(shù)據(jù)類型都是可以調(diào)用的
紅色的是 函數(shù)類型數(shù)據(jù)可以調(diào)用的
黑色的是 Map,數(shù)組 Array,通道 Chan,指針 Ptr 或者 切片Slice 可以調(diào)用的
藍(lán)色的是結(jié)構(gòu)體調(diào)用的
黃色的是通道 channel 類型調(diào)用的
reflect.Value
實際上是一個 struct,根據(jù)這個 struct 還關(guān)聯(lián)了一組方法,這里面存放了數(shù)據(jù)類型和具體的數(shù)據(jù),通過查看其數(shù)據(jù)結(jié)構(gòu)就可以看出
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
看到此處的 unsafe.Pointer 是不是很熟悉,底層自然就可以將 unsafe.Pointer
轉(zhuǎn)換成 uintptr
,然后再修改其數(shù)據(jù)后,再轉(zhuǎn)換回來,對于 Go 指針不太熟悉的可以查看這篇文章:
GO 中的指針?
寫一個簡單的 demo 就可以簡單的獲取到變量的數(shù)據(jù)類型和值
func main() { var demoStr string = "now reflect"
fmt.Println("type:", reflect.TypeOf(demoStr))
fmt.Println("value:", reflect.ValueOf(demoStr))
}
我們可以通過將 reflect.Value
類型轉(zhuǎn)換成我們具體的數(shù)據(jù)類型,因為 reflect.Value
中有對應(yīng)的 typ *rtype
以及 ptr unsafe.Pointer
例如我們可以 通過 reflect.Value
對象的 interface() 方法來處理
func main() { var demoStr string = "now reflect"
fmt.Println("type:", reflect.TypeOf(demoStr))
fmt.Println("value:", reflect.ValueOf(demoStr)) var res string
res = reflect.ValueOf(demoStr).Interface().(string)
fmt.Println("res == ",res)
}
首先我們看上書的 demo 代碼,傳入 TypeOf和 ValueOf的變量實際上也是一個拷貝,那么如果期望在反射類型的對象中修改其值,那么就需要拿到具體變量的地址然后再進(jìn)行修改,前提是這個變量是可寫的
舉個例子你就能明白
func main() {
var demoStr string = "now reflect"
v := reflect.ValueOf(demoStr)
fmt.Println("is canset ", v.CanSet())
//v.SetString("hello world") // 會panic
}
可以先調(diào)用 reflect.Value
對象的 CanSet
查看是否可寫,如果是可寫的,我們再寫,如果不可寫就不要寫了,否則會 panic
那么傳入變量的地址就可以修改了??
傳入地址的思路沒有毛病,但是我們?nèi)ピO(shè)置值的方式有問題,因此也會出現(xiàn)上述的 panic 情況
此處仔細(xì)看能夠明白,反射的對象 v 自然是不可修改的,我們應(yīng)該找到 reflect.Value
里面具體具體的數(shù)據(jù)指針,那么才是可以修改的,可以使用 reflect.Value
的 Elem方法
看上了上述案例可能會覺得那么簡單的案例,一演示就 ok,但是工作中一用就崩潰,那自然還是沒有融會貫通,說明還沒有消化好,再來一個工作中的例子
一個結(jié)構(gòu)體里面有 map,map 中的 key 是 string,value 是 []string
需求是訪問 結(jié)構(gòu)體中 hobby 字段對應(yīng)的 map key 為 sport的切片的第1個元素,并將其修改為 hellolworld
type RDemo struct {
Name string
Age int
Money float32
Hobby map[string][]string
}
func main() {
tmp := &RDemo{
Name: "xiaomiong",
Age: 18,
Money: 25.6,
Hobby: map[string][]string{
"sport": {"basketball", "football"},
"food": {"beef"},
},
}
v := reflect.ValueOf(tmp).Elem() // 拿到結(jié)構(gòu)體對象
h := v.FieldByName("Hobby") // 拿到 Hobby 對象
h2 := h.MapKeys()[0] // 拿到 Hobby 的第 0 個key
fmt.Println("key1 name == ",h2.Interface().(string))
sli := h.MapIndex(h2) // 拿到 Hobby 的第 0 個key對應(yīng)的對象
str := sli.Index(1) // 拿到切片的第 1 個對象
fmt.Println(str.CanSet())
str.SetString("helloworld")
fmt.Println("tmp == ",tmp)
}
可以看到上述案例運(yùn)行之后有時可以運(yùn)行成功,有時會出現(xiàn) panic 的情況,相信細(xì)心的 xdm 就可以看出來,是因為 map 中的 key 是 無序的導(dǎo)致的,此處也提醒一波,使用 map 的時候要注意這一點
看上述代碼,是不是就能夠明白咱們使用反射去找到對應(yīng)的數(shù)據(jù)類型,然后按照數(shù)據(jù)類型進(jìn)行處理數(shù)據(jù)的過程了呢
有需要的話,可以慢慢的去熟練反射包中涉及的函數(shù),重點是要了解其三個規(guī)則,對象轉(zhuǎn)換方式,訪問方式,以及數(shù)據(jù)修改方式
那么通過上述案例,可以知道關(guān)于反射中數(shù)據(jù)類型和數(shù)據(jù)指針對應(yīng)的值是相當(dāng)重要的,不同的數(shù)據(jù)類型能夠用哪些函數(shù)這個需要注意,否則用錯直接就會 panic
來看 TypeOf的接口中涉及的數(shù)據(jù)結(jié)構(gòu)
在 reflect 包中 rtype
是非常重要的,Go 中所有的類型都會包含這個結(jié)構(gòu),所以咱們反射可以應(yīng)用起來,結(jié)構(gòu)如下
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte
str nameOff
ptrToThis typeOff
}
其中可以看到此處的 rtype
的結(jié)構(gòu)保持和 runtime/type.go
一致 ,都是關(guān)于數(shù)據(jù)類型的表示,以及對應(yīng)的指針,關(guān)于這一塊的說明和演示可以查看文末的 interface{} 處的內(nèi)容
從 ValueOf 的源碼中,我們可以看到,重要的是 emptyInterface 結(jié)構(gòu)
// emptyInterface is the header for an interface{} value.type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}復(fù)制代碼
emptyInterface結(jié)構(gòu)中有 rtype
類型的指針, word自然是對應(yīng)的數(shù)據(jù)的地址了
reflect.Value
對象中的方法也是非常的多,用起來和上述說到的 reflect.Type
接口中的功能類似
關(guān)于源碼中涉及到的方法,就不再過多的贅述了,更多的還是需要自己多多實踐才能體會的更好
殊不知,此處的 reflect.Value
也是可以轉(zhuǎn)換成 reflect.Type
,可以查看源碼中 reflect\value.go
的 func (v Value) Type() Type {
其中 reflect.Value
,reflect.Type
,和任意數(shù)據(jù)類型
可以相互這樣來轉(zhuǎn)換
如下圖:
關(guān)于“Golang中的反射規(guī)則是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。