文章目錄
- 反射
-
- 反射類型Type
-
- 指針
- 結構體
- 反射值Value
-
- 結構體
- 空與有效性判斷
- 修改值
- 函數調用
- 反射三定律
- interface
-
- 底層結構
-
- iface
- eface
反射是一種讓程式可以在運作時( runtime )檢查其資料結構的能力,通過反射可以擷取豐富的類型資訊。
反射
Go語言提供了reflect 包來通路程式的反射資訊;定義了兩個重要的類型Type和Value:
- reflect.TypeOf:擷取任意值的類型對象(reflect.Type);
- reflect.ValueOf:獲得值的反射值對象(reflect.Value);
反射類型Type
Go語言程式中的類型(Type)指的是系統原生資料類型(如 int、string、bool、float32 等),以及使用 type 關鍵字定義的類型;而反射種類(Kind)是指對象的歸屬分類:
type Kind uint
const (
Invalid Kind = iota // 非法類型
Bool // 布爾型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 無符号整型
Uint8 // 無符号8位整型
Uint16 // 無符号16位整型
Uint32 // 無符号32位整型
Uint64 // 無符号64位整型
Uintptr // 指針
Float32 // 單精度浮點數
Float64 // 雙精度浮點數
Complex64 // 64位複數類型
Complex128 // 128位複數類型
Array // 數組
Chan // 通道
Func // 函數
Interface // 接口
Map // 映射
Ptr // 指針
Slice // 切片
String // 字元串
Struct // 結構體
UnsafePointer // 底層指針
)
指針
對指針指向的對象,可通過reflect.Elem() 方法擷取這個指針指向的元素類型(等效于對指針類型變量做了一個
*
操作)。
func reflectStruct() {
type Cat struct {
}
aCat := &Cat{}
// 擷取結構體執行個體的反射類型對象
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'
結構體
對結構體對象,擷取對象資訊後,可通過NumField() 和 Field() 方法獲得結構體成員的詳細資訊。
方法 | 說明 |
---|---|
Field(i int) StructField | 根據索引傳回索引對應字段的資訊 |
NumField() int | 傳回結構體成員字段數量 |
FieldByName(name string) (StructField, bool) | 根據給定字元串傳回字元串對應的結構體字段的資訊,沒有找到時 bool 傳回 false |
FieldByIndex(index []int) StructField | 多層成員通路時,根據 []int 提供的每個結構體的字段索引,傳回字段的資訊,沒有找到時傳回零值 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根據比對函數比對需要的字段 |
字段資訊中含有:
type StructField struct {
Name string // 字段名
PkgPath string // 字段在結構體中的路徑
Type Type // 字段的反射類型 reflect.Type
Tag StructTag // 字段的結構體标簽
Offset uintptr // 字段在結構體中的相對偏移
Index []int // FieldByIndex中的索引順序
Anonymous bool // 是否為匿名字段
}
擷取字段的名稱與tag:
func reflectStructField() {
// 聲明一個空結構體
type Cat struct {
Name string
// 帶有結構體tag的字段
Type int `json:"type" id:"100"`
}
aCat := Cat{Name: "mimi", Type: 1}
// 擷取結構體執行個體的反射類型對象
typeOfCat := reflect.TypeOf(aCat)
// 周遊結構體所有成員
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
通過下面幾種方法從反射值對象 reflect.Value 中擷取原值
方法名 | 說 明 |
---|---|
Interface() interface {} | 将值以 interface{} 類型傳回,可以通過類型斷言轉換為指定類型 |
Int() int64 | 将值以 int 類型傳回,所有有符号整型均可以此方式傳回 |
Uint() uint64 | 将值以 uint 類型傳回,所有無符号整型均可以此方式傳回 |
Float() float64 | 将值以雙精度(float64)類型傳回,所有浮點數(float32、float64)均可以此方式傳回 |
Bool() bool | 将值以 bool 類型傳回 |
Bytes() []bytes | 将值以位元組數組 []bytes 類型傳回 |
String() string | 将值以字元串類型傳回 |
通過反射擷取變量的值:
func reflectValue() {
var a int = 1024
// 擷取變量a的反射值對象
valueOfA := reflect.ValueOf(a)
// 擷取interface{}類型的值, 通過類型斷言轉換
var getA int = valueOfA.Interface().(int)
// 擷取64位的值, 強制類型轉換為int類型
var getA2 int = int(valueOfA.Int())
fmt.Println(getA, getA2)
}
// 1024 1024
結構體
反射值對象(reflect.Value)提供對結構體通路的方法,通過這些方法可以完成對結構體任意值的通路:
方 法 | 備 注 |
---|---|
Field(i int) Value | 根據索引,傳回索引對應的結構體成員字段的反射值對象 |
NumField() int | 傳回結構體成員字段數量 |
FieldByName(name string) Value | 根據給定字元串傳回字元串對應的結構體字段,沒有找到時傳回零值 |
FieldByIndex(index []int) Value | 多層成員通路時,根據 []int 提供的每個結構體的字段索引,傳回字段的值; 沒有找到時傳回零值 |
FieldByNameFunc(match func(string) bool) Value | 根據比對函數比對需要的字段,沒有找到時傳回零值 |
空與有效性判斷
反射值對象(reflect.Value)提供一系列方法進行零值和空判定:
方 法 | 說 明 |
---|---|
IsNil() bool | 是否為 nil,隻對通道、函數、接口、map、指針或切片有效(否則會panic) |
IsValid() bool | 是否有效,當值本身非法時(不包含任何值,或值為 nil),傳回 false |
修改值
通過反射修改變量值的前提條件之一:這個值必須可以被尋址,簡單地說就是這個變量必須能被修改。結構體成員中,如果字段沒有被導出,即便也可以被通路,也不能通過反射修改。
方法名 | 備 注 |
---|---|
Elem() Value | 取值指向的元素值(類似于 操作);對指針或接口時發生panic |
Addr() Value | 對可尋址的值傳回其位址(類似于 操作);當值不可尋址時發生panic |
CanAddr() bool | 表示值是否可尋址 |
CanSet() bool | 傳回值能否被修改;要求值可尋址且是導出的字段 |
修改結構體字段的值(需要結構體位址,與導出字段):
func reflectModifyValue() {
type Dog struct {
LegCount int
}
// 擷取dog執行個體位址的反射值對象
valueOfDog := reflect.ValueOf(&Dog{})
// 取出dog執行個體位址的元素
valueOfDog = valueOfDog.Elem()
vLegCount := valueOfDog.FieldByName("LegCount")
vLegCount.SetInt(4)
fmt.Println(vLegCount.Int())
}
函數調用
如果反射值對象(reflect.Value)為函數,可以通過Call()調用:參數使用反射值對象的切片[]reflect.Value構造後傳入,傳回值通過[]reflect.Value傳回。
func add(a, b int) int {
return a + b
}
func reflectFunction() {
// 将函數包裝為反射值對象
funcValue := reflect.ValueOf(add)
// 構造函數參數, 傳入兩個整型值
paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
// 反射調用函數
retList := funcValue.Call(paramList)
// 擷取第一個傳回值, 取整數值
fmt.Println(retList[0].Int())
}
反射三定律
官方提供了三條定律來說明反射:
- 反射可将interface類型變量轉換成反射對象;
- 反射可将反射對象還原成interface對象;
- 要修改反射對象,其值必須是可寫的(反射其指針類型);
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實作抽象的一個非常強大的工具;當向接口指派時,接口會存儲實體的類型資訊;反射就是通過接口的類型資訊實作的。
interface類型是一種特殊類型,代表方法集合;可存放任何實作了其方法的值(實際存放的是
(value,type)
對)。reflect包中實作了反射的各種函數:
- 提取interface的value的方法
;reflect.ValueOf()->reflect.Value
- 提取interface的type的方法
;reflect.TypeOf()->reflect.Type
空interface類型(
interface{}
)的方法集為空,是以可認為任何類型都實作了該接口;是以其可存放任何值。
底層結構
interface底層結構分為iface和eface描述接口,其差別是eface為不包含任何方法的空接口。
iface
iface定義如下:
-
指向一個tab
實體的指針:表示接口的類型(賦給此接口的實體類型);itab
-
指向接口具體的值:一般而言是一個指向堆記憶體的指針。data
type iface struct {
tab *itab
data unsafe.Pointer
}
itab結構:
-
字段描述了實體的類型:包括記憶體對齊方式,大小等;_type
-
字段則描述了接口的類型;inter
-
字段放置是實體類中和接口方法對應(實體中其他方法不在此處)的方法位址,以實作接口調用方法的動态分派;一般在每次給接口指派發生轉換時會更新此表。fun
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32
bad bool
inhash bool
unused [2]byte
fun [1]uintptr
}
iface結構全貌圖:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNiNx8FesU2cfdGLwczX0xiRGZkRGZ0Xy9GbvNGLwIzXlpXazxSdW52Y6xGWZNDeXlVQClGVF5UMR9Fd4VGdsATNfd3bkFGazxSUhxGatJGbwhFT1Y0Mk9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0UzYmlzMmVzN0ImYyUGNhFWYyQDOlNTZ4Y2M5cTNiZ2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
eface
eface結構:隻維護了一個
_type
字段,表示空接口所承載的具體的實體類型。
type eface struct {
_type *_type
data unsafe.Pointer
}