1 概述

類似於 Java,Go 語言也支持反射。支持反射的語言可以在運行時對程序進行訪問和修改。反射的原理是在程序編譯期將反射信息(如類型信息、結構體信息等)整合到程序中,並給提供給程序訪問反射信息的操作介面,這樣在程序運行期間就可以獲取該反射信息,甚至支持修改操作。

Go 語言使用 reflect 包支持反射。

本文介紹與類型結構相關的反射操作。

2 獲取類型

使用 reflect.TypeOf() 函數可以獲得任意值的類型反射對象。演示為:

type Stu struct {
}
var v *Stu
typeV := reflect.TypeOf(v)
fmt.Println(typeV)
// *main.Stu

其中,typeV是 reflect.Type 類型的實例。

3 獲取基礎類型(類別)

基礎類型,也稱之為類別。例如 type Stu struct,從類型上看是 Stu 類型,如果從基礎類型(類別)的角度去看,就是 struct。當需要區分一個大類類別時,就會用到基礎類型的概念。可以通過 typeV.Kind() 方法獲取對應的基礎類型。演示為:

type Stu struct {
}
var v Stu
typeV := reflect.TypeOf(v)
// 同時輸出類型名和基礎類型
fmt.Println(typeV.Name(), typeV.Kind())
// Stu struct

Go 語言的 reflect 包定義瞭如下的基礎類型:

來自文件src/reflect/type.go
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 // 底層指針
)

可見指的是原生類型,而不是自定義類型。

4 指針引用的元素類型

可以使用指針類型的反射得到其指向的元素的具體類型,使用 Elem() Type 來實現,演示為:

type Stu struct {
}
var v *Stu
typeV := reflect.TypeOf(v)
fmt.Println(typeV)
// *main.Stu
fmt.Println(typeV.Kind())
// 基礎類型為 ptr 指針
// ptr
fmt.Println(typeV.Elem())
// 指向的元素類型為 main.Stu
// main.Stu
fmt.Println(typeV.Elem().Kind())
// main.Stu的基礎類型為 struct
// struct

.Elem() 方法會得到 reflect.Type 類型的返回值,因此可以繼續調用 .Kind() 得到基礎類型。

5 結構體信息

若反射的類型為結構體,可以獲取其成員信息。涉及幾個方法:

  • NumField() int,欄位數量
  • Field(i int) StructField,通過索引確定獲取欄位的反射
  • NumMethod() int,方法數量
  • Method(int) Method,通過索引獲取方法的反射

演示為:

type Stu struct {
Name string
Sn string
}

func (this *Stu) SetName() {
}
func (this *Stu) SetSn() {
}

func main() {
v := Stu{}
typeV := reflect.TypeOf(v)
fmt.Println(typeV.NumField())
for i, c := 0, typeV.NumField(); i < c; i++ {
fmt.Println(typeV.Field(i))
}
vp := &v // ? 為什麼必須要是引用呢 ?
typeVP := reflect.TypeOf(vp)
fmt.Println(typeVP.NumMethod())
for i, c := 0, typeV.NumMethod(); i < c; i++ {
fmt.Println(typeVP.Method(i))
}
}
// 以下為輸出結果
2
{Name string 0 [0] false}
{Sn string 16 [1] false}
2
{SetName func(*main.Stu) <func(*main.Stu) Value> 0}
{SetSn func(*main.Stu) <func(*main.Stu) Value> 1}

做本案例時,發現對於方法反射的獲取,要基於結構體指針纔可以,目前不解,需要在深入下。

我們獲取的屬性和方法分別屬於 reflect.StructFieldreflect.Method 類型,若需要接續獲取屬性欄位或方法的信息,可以使用該類型定義的方法完成。定義如下,供參考:

type StructField struct {
Name string // 欄位名
PkgPath string // 非導出欄位的包路徑,對導出欄位該欄位為""
Type Type // 欄位類型
Tag StructTag // 欄位標籤
Offset uintptr // 欄位在結構體中的位元組偏移量
Index []int // 用於Type.FieldByIndex時的索引切片
Anonymous bool // 是否匿名欄位
}

type Method struct {
Name string // 方法名
PkgPath string // 非導出方法的包路徑,對導出方法該欄位為""
Type Type // 方法類型
Func Value // 方法值
Index int // 方法索引
}

也支持: FieldByName(name string) (StructField, bool),通過欄位名字確定欄位的反射 MethodByName(string) (Method, bool),通過方法名字確定方法的反射。

6 結構體標籤

結構體標籤,Struct Tag,指的是為欄位增加額外的屬性,利用反射可獲取到這些屬性,進而完成特定操作。例如:

type Stu struct {
Name string `json:"name" bson:"name"`
Sn string `json:"sn" bson:"sn"`
}

欄位後反引號包裹的就是欄位的標籤。上面的標籤是一個常用的格式,在做結構體序列化時經常使用。

利用反射獲取標籤內容,先獲取欄位,再獲取欄位上的標籤:

type Stu struct {
Name string `json:"j_name" bson:"b_name"`
Sn string `json:"j_sn" bson:"b_sn"`
}

func main() {
var v Stu
typeV := reflect.TypeOf(v)
for i, c := 0, typeV.NumField(); i < c; i++ {
fmt.Println(typeV.Field(i).Tag.Get("json"), typeV.Field(i).Tag.Get("bson"))
}
}
// 輸出
j_name b_name
j_sn b_sn

標籤語法是key:value結構。(也可以字元串,key:value 更長用,信息量更大)。StructField.Tag 可以獲取欄位的標籤,.Get() 方法可以獲取具體內容。

演示,利用標籤 json 編碼我們的結構體對象,需要 encoding/json 包:

type Stu struct {
Name string `json:"j_name" bson:"b_name"`
Sn string `json:"j_sn" bson:"b_sn"`
}
func main() {
var v = Stu{
"Hank",
"Kang-007",
}
json, err := json.Marshal(v)
fmt.Println(string(json), err)
// {"j_name":"Hank","j_sn":"Kang-007"} <nil>
}

注意上面的 json 中的欄位,並不是我們的欄位Name和Sn,而是標籤中定義的j_name, j_sn。json.Marshal 方法就讀取了欄位的tag,確定了欄位的名稱。除了欄位名稱提示外,json.Marshal 還支持 json:"j_sn,string,omitempty" 表示設置名稱,類型,忽略空值等操作。

也可利用 json 轉換得到結構體對象,繼續使用上面的結構體Stu:

var u Stu
if nil == json.Unmarshal(str, &u) {
fmt.Println(u)
// {Hank Kang-007}
}

完! 原文出自:小韓說課 微信關註:小韓說課

weixin.qq.com/r/xjm0rK- (二維碼自動識別)


推薦閱讀:
相關文章