Go語言反射之型別反射
文章目錄
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.StructField
,reflect.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}
}
完!
原文出自:小韓說課
微信關注:小韓說課