GoLang反射機(jī)制深入講解
反射
Go語言提供了reflect 包來訪問程序的反射信息;定義了兩個(gè)重要的類型Type和Value:
- reflect.TypeOf:獲取任意值的類型對(duì)象(reflect.Type);
- reflect.ValueOf:獲得值的反射值對(duì)象(reflect.Value);
反射類型Type
Go語言程序中的類型(Type)指的是系統(tǒng)原生數(shù)據(jù)類型(如 int、string、bool、float32 等),以及使用 type 關(guān)鍵字定義的類型;而反射種類(Kind)是指對(duì)象的歸屬分類:
type Kind uint const ( Invalid Kind = iota // 非法類型 Bool // 布爾型 Int // 有符號(hào)整型 Int8 // 有符號(hào)8位整型 Int16 // 有符號(hào)16位整型 Int32 // 有符號(hào)32位整型 Int64 // 有符號(hào)64位整型 Uint // 無符號(hào)整型 Uint8 // 無符號(hào)8位整型 Uint16 // 無符號(hào)16位整型 Uint32 // 無符號(hào)32位整型 Uint64 // 無符號(hào)64位整型 Uintptr // 指針 Float32 // 單精度浮點(diǎn)數(shù) Float64 // 雙精度浮點(diǎn)數(shù) Complex64 // 64位復(fù)數(shù)類型 Complex128 // 128位復(fù)數(shù)類型 Array // 數(shù)組 Chan // 通道 Func // 函數(shù) Interface // 接口 Map // 映射 Ptr // 指針 Slice // 切片 String // 字符串 Struct // 結(jié)構(gòu)體 UnsafePointer // 底層指針 )
指針
對(duì)指針指向的對(duì)象,可通過reflect.Elem() 方法獲取這個(gè)指針指向的元素類型(等效于對(duì)指針類型變量做了一個(gè)*
操作)。
func reflectStruct() { type Cat struct { } aCat := &Cat{} // 獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象 typeOfCat := reflect.TypeOf(aCat) fmt.Printf("ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind()) // 取類型的元素 typeOfCat = typeOfCat.Elem() fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind()) typeOfCat = reflect.TypeOf(*aCat) fmt.Printf("*ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind()) } // ptr name:'' kind:'ptr' // element name: 'Cat', element kind: 'struct' // *ptr name:'Cat' kind:'struct'
結(jié)構(gòu)體
對(duì)結(jié)構(gòu)體對(duì)象,獲取對(duì)象信息后,可通過NumField() 和 Field() 方法獲得結(jié)構(gòu)體成員的詳細(xì)信息。
方法 | 說明 |
---|---|
Field(i int) StructField | 根據(jù)索引返回索引對(duì)應(yīng)字段的信息 |
NumField() int | 返回結(jié)構(gòu)體成員字段數(shù)量 |
FieldByName(name string) (StructField, bool) | 根據(jù)給定字符串返回字符串對(duì)應(yīng)的結(jié)構(gòu)體字段的信息,沒有找到時(shí) bool 返回 false |
FieldByIndex(index []int) StructField | 多層成員訪問時(shí),根據(jù) []int 提供的每個(gè)結(jié)構(gòu)體的字段索引,返回字段的信息,沒有找到時(shí)返回零值 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根據(jù)匹配函數(shù)匹配需要的字段 |
字段信息中含有:
type StructField struct { Name string // 字段名 PkgPath string // 字段在結(jié)構(gòu)體中的路徑 Type Type // 字段的反射類型 reflect.Type Tag StructTag // 字段的結(jié)構(gòu)體標(biāo)簽 Offset uintptr // 字段在結(jié)構(gòu)體中的相對(duì)偏移 Index []int // FieldByIndex中的索引順序 Anonymous bool // 是否為匿名字段 }
獲取字段的名稱與tag:
func reflectStructField() { // 聲明一個(gè)空結(jié)構(gòu)體 type Cat struct { Name string // 帶有結(jié)構(gòu)體tag的字段 Type int `json:"type" id:"100"` } aCat := Cat{Name: "mimi", Type: 1} // 獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象 typeOfCat := reflect.TypeOf(aCat) // 遍歷結(jié)構(gòu)體所有成員 for i := 0; i < typeOfCat.NumField(); i++ { fieldType := typeOfCat.Field(i) fmt.Printf("Field-%v: name: %v tag: '%v'\n", i, fieldType.Name, fieldType.Tag) } // 通過字段名, 找到字段類型信息 if catType, ok := typeOfCat.FieldByName("Type"); ok { fmt.Println("Field Tag: ", catType.Tag.Get("json"), catType.Tag.Get("id")) } } // Field-0: name: Name tag: '' // Field-1: name: Type tag: 'json:"type" id:"100"' // Field Tag: type 100
反射值Value
通過下面幾種方法從反射值對(duì)象 reflect.Value 中獲取原值
方法名 | 說 明 |
---|---|
Interface() interface {} | 將值以 interface{} 類型返回,可以通過類型斷言轉(zhuǎn)換為指定類型 |
Int() int64 | 將值以 int 類型返回,所有有符號(hào)整型均可以此方式返回 |
Uint() uint64 | 將值以 uint 類型返回,所有無符號(hào)整型均可以此方式返回 |
Float() float64 | 將值以雙精度(float64)類型返回,所有浮點(diǎn)數(shù)(float32、float64)均可以此方式返回 |
Bool() bool | 將值以 bool 類型返回 |
Bytes() []bytes | 將值以字節(jié)數(shù)組 []bytes 類型返回 |
String() string | 將值以字符串類型返回 |
通過反射獲取變量的值:
func reflectValue() { var a int = 1024 // 獲取變量a的反射值對(duì)象 valueOfA := reflect.ValueOf(a) // 獲取interface{}類型的值, 通過類型斷言轉(zhuǎn)換 var getA int = valueOfA.Interface().(int) // 獲取64位的值, 強(qiáng)制類型轉(zhuǎn)換為int類型 var getA2 int = int(valueOfA.Int()) fmt.Println(getA, getA2) } // 1024 1024
結(jié)構(gòu)體
反射值對(duì)象(reflect.Value)提供對(duì)結(jié)構(gòu)體訪問的方法,通過這些方法可以完成對(duì)結(jié)構(gòu)體任意值的訪問:
方 法 | 備 注 |
---|---|
Field(i int) Value | 根據(jù)索引,返回索引對(duì)應(yīng)的結(jié)構(gòu)體成員字段的反射值對(duì)象 |
NumField() int | 返回結(jié)構(gòu)體成員字段數(shù)量 |
FieldByName(name string) Value | 根據(jù)給定字符串返回字符串對(duì)應(yīng)的結(jié)構(gòu)體字段,沒有找到時(shí)返回零值 |
FieldByIndex(index []int) Value | 多層成員訪問時(shí),根據(jù) []int 提供的每個(gè)結(jié)構(gòu)體的字段索引,返回字段的值; 沒有找到時(shí)返回零值 |
FieldByNameFunc(match func(string) bool) Value | 根據(jù)匹配函數(shù)匹配需要的字段,沒有找到時(shí)返回零值 |
空與有效性判斷
反射值對(duì)象(reflect.Value)提供一系列方法進(jìn)行零值和空判定:
方 法 | 說 明 |
---|---|
IsNil() bool | 是否為 nil,只對(duì)通道、函數(shù)、接口、map、指針或切片有效(否則會(huì)panic) |
IsValid() bool | 是否有效,當(dāng)值本身非法時(shí)(不包含任何值,或值為 nil),返回 false |
修改值
通過反射修改變量值的前提條件之一:這個(gè)值必須可以被尋址,簡單地說就是這個(gè)變量必須能被修改。結(jié)構(gòu)體成員中,如果字段沒有被導(dǎo)出,即便也可以被訪問,也不能通過反射修改。
方法名 | 備 注 |
---|---|
Elem() Value | 取值指向的元素值(類似于*操作);對(duì)指針或接口時(shí)發(fā)生panic |
Addr() Value | 對(duì)可尋址的值返回其地址(類似于&操作);當(dāng)值不可尋址時(shí)發(fā)生panic |
CanAddr() bool | 表示值是否可尋址 |
CanSet() bool | 返回值能否被修改;要求值可尋址且是導(dǎo)出的字段 |
修改結(jié)構(gòu)體字段的值(需要結(jié)構(gòu)體地址,與導(dǎo)出字段):
func reflectModifyValue() { type Dog struct { LegCount int } // 獲取dog實(shí)例地址的反射值對(duì)象 valueOfDog := reflect.ValueOf(&Dog{}) // 取出dog實(shí)例地址的元素 valueOfDog = valueOfDog.Elem() vLegCount := valueOfDog.FieldByName("LegCount") vLegCount.SetInt(4) fmt.Println(vLegCount.Int()) }
函數(shù)調(diào)用
如果反射值對(duì)象(reflect.Value)為函數(shù),可以通過Call()調(diào)用:參數(shù)使用反射值對(duì)象的切片[]reflect.Value構(gòu)造后傳入,返回值通過[]reflect.Value返回。
func add(a, b int) int { return a + b } func reflectFunction() { // 將函數(shù)包裝為反射值對(duì)象 funcValue := reflect.ValueOf(add) // 構(gòu)造函數(shù)參數(shù), 傳入兩個(gè)整型值 paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} // 反射調(diào)用函數(shù) retList := funcValue.Call(paramList) // 獲取第一個(gè)返回值, 取整數(shù)值 fmt.Println(retList[0].Int()) }
反射三定律
官方提供了三條定律來說明反射:
- 反射可將interface類型變量轉(zhuǎn)換成反射對(duì)象;
- 反射可將反射對(duì)象還原成interface對(duì)象;
- 要修改反射對(duì)象,其值必須是可寫的(反射其指針類型);
var x float64 = 3.4 v := reflect.ValueOf(x) // v is reflext.Value var y float64 = v.Interface().(float64) fmt.Println("value:", y) // 3.4
值類型不能直接修改,可通過傳遞地址并通過Elem獲取后修改:
var x float64 = 3.4 v := reflect.ValueOf(&x) v.Elem().SetFloat(7.8) fmt.Println("x :", v.Elem().Interface()) // 7.8
interface
interface是Go實(shí)現(xiàn)抽象的一個(gè)非常強(qiáng)大的工具;當(dāng)向接口賦值時(shí),接口會(huì)存儲(chǔ)實(shí)體的類型信息;反射就是通過接口的類型信息實(shí)現(xiàn)的。
interface類型是一種特殊類型,代表方法集合;可存放任何實(shí)現(xiàn)了其方法的值(實(shí)際存放的是(value,type)
對(duì))。reflect包中實(shí)現(xiàn)了反射的各種函數(shù):
- 提取interface的value的方法
reflect.ValueOf()->reflect.Value
; - 提取interface的type的方法
reflect.TypeOf()->reflect.Type
;
空interface類型(interface{}
)的方法集為空,所以可認(rèn)為任何類型都實(shí)現(xiàn)了該接口;因此其可存放任何值。
底層結(jié)構(gòu)
interface底層結(jié)構(gòu)分為iface和eface描述接口,其區(qū)別是eface為不包含任何方法的空接口。
iface
iface定義如下:
tab
指向一個(gè)itab
實(shí)體的指針:表示接口的類型(賦給此接口的實(shí)體類型);data
指向接口具體的值:一般而言是一個(gè)指向堆內(nèi)存的指針。
type iface struct { tab *itab data unsafe.Pointer }
itab結(jié)構(gòu):
_type
字段描述了實(shí)體的類型:包括內(nèi)存對(duì)齊方式,大小等;inter
字段則描述了接口的類型;fun
字段放置是實(shí)體類中和接口方法對(duì)應(yīng)(實(shí)體中其他方法不在此處)的方法地址,以實(shí)現(xiàn)接口調(diào)用方法的動(dòng)態(tài)分派;一般在每次給接口賦值發(fā)生轉(zhuǎn)換時(shí)會(huì)更新此表。
type itab struct { inter *interfacetype _type *_type link *itab hash uint32 bad bool inhash bool unused [2]byte fun [1]uintptr }
iface結(jié)構(gòu)全貌圖:
eface
eface結(jié)構(gòu):只維護(hù)了一個(gè) _type
字段,表示空接口所承載的具體的實(shí)體類型。
type eface struct { _type *_type data unsafe.Pointer }
到此這篇關(guān)于GoLang反射機(jī)制深入講解的文章就介紹到這了,更多相關(guān)Go反射內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入了解Golang中的反射機(jī)制
- Go語言開發(fā)框架反射機(jī)制及常見函數(shù)示例詳解
- Go語言的反射機(jī)制詳解
- Golang學(xué)習(xí)之反射機(jī)制的用法詳解
- go語言通過反射獲取和設(shè)置結(jié)構(gòu)體字段值的方法
- 淺談Go語言中的結(jié)構(gòu)體struct & 接口Interface & 反射
- 談?wù)凣o語言的反射三定律
- 詳解Golang利用反射reflect動(dòng)態(tài)調(diào)用方法
- Go語言學(xué)習(xí)筆記之反射用法詳解
- go語言通過反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對(duì)應(yīng)的操作
- Go語言中反射的正確使用
- 揭秘Go語言中的反射機(jī)制
相關(guān)文章
最新版Golang?pprof使用詳解(引入、抓取、分析,圖文結(jié)合)
這篇文章主要介紹了最新版Golang?pprof使用詳解包括引入、抓取、分析,圖文結(jié)合,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-08-08Go中調(diào)用JS代碼(otto)的實(shí)現(xiàn)示例
Otto是一個(gè)用Go語言實(shí)現(xiàn)的JavaScript解釋器,可用于執(zhí)行和操作JavaScript代碼,適合在Go項(xiàng)目中執(zhí)行簡單的JS腳本,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-10-10Qt6.5 grpc組件使用 + golang grpc server
這篇文章主要介紹了Qt6.5 grpc組件使用+golang grpc server示例,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05Go項(xiàng)目中添加生成時(shí)間與版本信息的方法
本文主要介紹了Go項(xiàng)目中添加生成時(shí)間與版本信息的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04