1. 程式人生 > >Go語言反射之型別反射

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.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}
}

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