Go反射底層原理及數(shù)據(jù)結(jié)構(gòu)解析
1. 反射的引入與介紹
在計(jì)算機(jī)科學(xué)中,反射是指計(jì)算機(jī)程序在運(yùn)行時(shí)(Run time)可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力。用比喻來說,反射就是程序在運(yùn)行的時(shí)候能夠“觀察”并且修改自己的行為。
需要反射的 2 個(gè)常見場景:
- 有時(shí)你需要編寫一個(gè)函數(shù),但是并不知道傳給你的參數(shù)類型是什么,可能是沒約定好;也可能是傳入的類型很多,這些類型并不能統(tǒng)一表示。這時(shí)反射就會(huì)用的上了。
- 有時(shí)候需要根據(jù)某些條件決定調(diào)用哪個(gè)函數(shù),比如根據(jù)用戶的輸入來決定。這時(shí)就需要對(duì)函數(shù)和函數(shù)的參數(shù)進(jìn)行反射,在運(yùn)行期間動(dòng)態(tài)地執(zhí)行函數(shù)。
Java中動(dòng)態(tài)代理與AOP的實(shí)現(xiàn),就要借助這種操作。而對(duì)于我們Go語言來說,也提供了反射的機(jī)制。
有了前面對(duì)于interface{}底層數(shù)據(jù)結(jié)構(gòu)的了解,Go中的每個(gè)實(shí)例對(duì)象可以分為兩快,類型信息與數(shù)值信息。而我們Go中提供的反射機(jī)制,能分別拿到這兩塊信息。

數(shù)據(jù)interface中保存有結(jié)構(gòu)數(shù)據(jù),拿到該數(shù)據(jù)對(duì)應(yīng)的內(nèi)存地址,然后把該數(shù)據(jù)轉(zhuǎn)成interface,通過查看interface中的類型結(jié)構(gòu),就可以知道該數(shù)據(jù)的結(jié)構(gòu)了,其實(shí)以上就是Go反射通俗的原理。

2. 反射的數(shù)據(jù)結(jié)構(gòu)
type Type interface {
// 所有的類型都可以調(diào)用下面這些函數(shù)
// 此類型的變量對(duì)齊后所占用的字節(jié)數(shù)
Align() int
// 如果是 struct 的字段,對(duì)齊后占用的字節(jié)數(shù)
FieldAlign() int
// 返回類型方法集里的第 `i` (傳入的參數(shù))個(gè)方法
Method(int) Method
// 通過名稱獲取方法
MethodByName(string) (Method, bool)
// 獲取類型方法集里導(dǎo)出的方法個(gè)數(shù)
NumMethod() int
// 類型名稱
Name() string
// 返回類型所在的路徑,如:encoding/base64
PkgPath() string
// 返回類型的大小,和 unsafe.Sizeof 功能類似
Size() uintptr
// 返回類型的字符串表示形式
String() string
// 返回類型的類型值
Kind() Kind
// 類型是否實(shí)現(xiàn)了接口 u
Implements(u Type) bool
// 是否可以賦值給 u
AssignableTo(u Type) bool
// 是否可以類型轉(zhuǎn)換成 u
ConvertibleTo(u Type) bool
// 類型是否可以比較
Comparable() bool
// 下面這些函數(shù)只有特定類型可以調(diào)用
// 如:Key, Elem 兩個(gè)方法就只能是 Map 類型才能調(diào)用
// 類型所占據(jù)的位數(shù)
Bits() int
// 返回通道的方向,只能是 chan 類型調(diào)用
ChanDir() ChanDir
// 返回類型是否是可變參數(shù),只能是 func 類型調(diào)用
// 比如 t 是類型 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool
// 返回內(nèi)部子元素類型,只能由類型 Array, Chan, Map, Ptr, or Slice 調(diào)用
Elem() Type
// 返回結(jié)構(gòu)體類型的第 i 個(gè)字段,只能是結(jié)構(gòu)體類型調(diào)用
// 如果 i 超過了總字段數(shù),就會(huì) panic
Field(i int) StructField
// 返回嵌套的結(jié)構(gòu)體的字段
FieldByIndex(index []int) StructField
// 通過字段名稱獲取字段
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// 返回名稱符合 func 函數(shù)的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 獲取函數(shù)類型的第 i 個(gè)參數(shù)的類型
In(i int) Type
// 返回 map 的 key 類型,只能由類型 map 調(diào)用
Key() Type
// 返回 Array 的長度,只能由類型 Array 調(diào)用
Len() int
// 返回類型字段的數(shù)量,只能由類型 Struct 調(diào)用
NumField() int
// 返回函數(shù)類型的輸入?yún)?shù)個(gè)數(shù)
NumIn() int
// 返回函數(shù)類型的返回值個(gè)數(shù)
NumOut() int
// 返回函數(shù)類型的第 i 個(gè)值的類型
Out(i int) Type
// 返回類型結(jié)構(gòu)體的相同部分
common() *rtype
// 返回類型結(jié)構(gòu)體的不同部分
uncommon() *uncommonType
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}反射的實(shí)現(xiàn)和interface的組成很相似,都是由“類型”和“數(shù)據(jù)值”構(gòu)成,但是值得注意的是:interface的“類型”和“數(shù)據(jù)值”是在“一起的”,而反射的“類型”和“數(shù)據(jù)值”是分開的。
Type和Value提供了非常多的方法:例如獲取對(duì)象的屬性列表、獲取和修改某個(gè)屬性的值、對(duì)象所屬結(jié)構(gòu)體的名字、對(duì)象的底層類型(underlying type)等等
Go中的反射,在使用中最核心的就兩個(gè)函數(shù):
- reflect.TypeOf(x)
- reflect.ValueOf(x)
這兩個(gè)函數(shù)可以分別將給定的數(shù)據(jù)對(duì)象轉(zhuǎn)化為以上的Type和Value。這兩個(gè)都叫做反射對(duì)象
3. 如何通過反射對(duì)象來修改原數(shù)據(jù)對(duì)象的值?
在Go中,任何函數(shù)的參數(shù)都是值的拷貝,而非原數(shù)據(jù)。
反射函數(shù)reflect.ValueOf()也不例外。我們目前得到的反射對(duì)象,都是原對(duì)象的copy的反射對(duì)象,而非原對(duì)象本身,所以不可以修改到原對(duì)象.
那如何修改呢?
首先,在Go中要想讓函數(shù)“有副作用“,傳值必須傳指針類型的。
var x float64 = 5.7 v := reflect.ValueOf(&x)
時(shí)還不行,因?yàn)檫@樣反射對(duì)象對(duì)應(yīng)的是原數(shù)據(jù)對(duì)象的指針類型,必須要拿到當(dāng)前類型的值類型(*v)。 Go提供了另外一個(gè)方法Elem()
p := v.Elem() fmt.Println(p.CanSet()) // true p.SetFloat(6.6) fmt.Println(x) // 6.6
經(jīng)過以上操作,就可以修改原數(shù)據(jù)了,完整過程如下:
var x float64 = 5.7 v := reflect.ValueOf(&x) p := v.Elem() fmt.Println(p.CanSet()) // true p.SetFloat(6.6) fmt.Println(x) // 6.6
到此這篇關(guān)于Go反射底層原理及數(shù)據(jù)結(jié)構(gòu)解析的文章就介紹到這了,更多相關(guān) Go反射原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA如何自動(dòng)下載SSL證書并導(dǎo)入到本地
這篇文章主要介紹了JAVA如何自動(dòng)下載SSL證書并導(dǎo)入到本地問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Java Jedis NOAUTH Authentication required問題解決方法
這篇文章主要介紹了Java Jedis NOAUTH Authentication required問題解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
SpringBoot讀取properties中文亂碼解決方案
本文主要介紹了在Spring?Boot中讀取帶有中文字符串的application.properties文件時(shí)遇到亂碼問題的解決方案,具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12
SpringBoot集成WebSocket的兩種方式(JDK內(nèi)置版和Spring封裝版)
這篇文章主要介紹了SpringBoot集成WebSocket的兩種方式,這兩種方式為JDK內(nèi)置版和Spring封裝版,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
springboot2.x默認(rèn)使用的代理是cglib代理操作
這篇文章主要介紹了springboot2.x默認(rèn)使用的代理是cglib代理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08

