Golang 反射的使用

聲明: 轉(zhuǎn)載自golang reflect包,反射學(xué)習(xí)與實(shí)踐

Go 語(yǔ)言反射的三大法則,其中包括:

  • 從 interface{} 變量可以反射出反射對(duì)象;
  • 從反射對(duì)象可以獲取 interface{} 變量;
  • 要修改反射對(duì)象,其值必須可設(shè)置;

從反射對(duì)象到接口值的過(guò)程就是從接口值到反射對(duì)象的鏡面過(guò)程,兩個(gè)過(guò)程都需要經(jīng)歷兩次轉(zhuǎn)換:

  • 從接口值到反射對(duì)象:
    • 從基本類型到接口類型的類型轉(zhuǎn)換;
    • 從接口類型到反射對(duì)象的轉(zhuǎn)換;
  • 從反射對(duì)象到接口值:
    • 反射對(duì)象轉(zhuǎn)換成接口類型;
    • 通過(guò)顯式類型轉(zhuǎn)換變成原始類型;

Type,Value

反射包中的所有方法基本都是圍繞著TypeValue這兩個(gè)類型設(shè)計(jì)的。我們通過(guò)reflect.TypeOfreflect.ValueOf可以將一個(gè)普通的變量轉(zhuǎn)換成『反射』包中提供的TypeValue,隨后就可以使用反射包中的方法對(duì)它們進(jìn)行復(fù)雜的操作。

類型Type是反射包定義的一個(gè)接口,我們可以使用 reflect.TypeOf 函數(shù)獲取任意變量的的類型,Type 接口中定義了一些有趣的方法,MethodByName 可以獲取當(dāng)前類型對(duì)應(yīng)方法的引用、Implements 可以判斷當(dāng)前類型是否實(shí)現(xiàn)了某個(gè)接口:

type Type interface {
    // 變量的內(nèi)存對(duì)齊,返回 rtype.align
    Align() int

    // struct 字段的內(nèi)存對(duì)齊,返回 rtype.fieldAlign
    FieldAlign() int

    // 根據(jù)傳入的 i,返回方法實(shí)例,表示類型的第 i 個(gè)方法
    Method(int) Method

    // 根據(jù)名字返回方法實(shí)例,這個(gè)比較常用
    MethodByName(string) (Method, bool)

    // 返回類型方法集中可導(dǎo)出的方法的數(shù)量
    NumMethod() int

    // 只返回類型名,不含包名
    Name() string

    // 返回導(dǎo)入路徑,即 import 路徑
    PkgPath() string

    // 返回 rtype.size 即類型大小,單位是字節(jié)數(shù)
    Size() uintptr

    // 返回類型名字,實(shí)際就是 PkgPath() + Name()
    String() string

    // 返回 rtype.kind,描述一種基礎(chǔ)類型
    Kind() Kind

    // 檢查當(dāng)前類型有沒有實(shí)現(xiàn)接口 u
    Implements(u Type) bool

    // 檢查當(dāng)前類型能不能賦值給接口 u
    AssignableTo(u Type) bool

    // 檢查當(dāng)前類型能不能轉(zhuǎn)換成接口 u 類型
    ConvertibleTo(u Type) bool

    // 檢查當(dāng)前類型能不能做比較運(yùn)算,其實(shí)就是看這個(gè)類型底層有沒有綁定 typeAlg 的 equal 方法。
    // 打??!不要去搜 typeAlg 是什么,不然你會(huì)陷進(jìn)去的!先把本文看完。
    Comparable() bool

    // 返回類型的位大小,但不是所有類型都能調(diào)這個(gè)方法,不能調(diào)的會(huì) panic
    Bits() int

    // 返回 channel 類型的方向,如果不是 channel,會(huì) panic
    ChanDir() ChanDir

    // 返回函數(shù)類型的最后一個(gè)參數(shù)是不是可變數(shù)量的,"..." 就這樣的,同樣,如果不是函數(shù)類型,會(huì) panic
    IsVariadic() bool

    // 返回所包含元素的類型,只有 Array, Chan, Map, Ptr, Slice 這些才能調(diào),其他類型會(huì) panic。
    // 這不是廢話嗎。。其他類型也沒有包含元素一說(shuō)。
    Elem() Type

    // 返回 struct 類型的第 i 個(gè)字段,不是 struct 會(huì) panic,i 越界也會(huì) panic
    Field(i int) StructField

    // 跟上邊一樣,不過(guò)是嵌套調(diào)用的,比如 [1, 2] 就是說(shuō)返回當(dāng)前 struct 的第1個(gè)struct 的第2個(gè)字段,適用于 struct 本身嵌套的類型
    FieldByIndex(index []int) StructField

    // 按名字找 struct 字段,第二個(gè)返回值 ok 表示有沒有
    FieldByName(name string) (StructField, bool)

    // 按函數(shù)名找 struct 字段,因?yàn)?struct 里也可能有類型是 func 的嘛
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    
    // 返回函數(shù)第 i 個(gè)參數(shù)的類型,不是 func 會(huì) panic
    In(i int) Type

    // 返回 map 的 key 的類型,不是 map 會(huì) panic
    Key() Type

    // 返回 array 的長(zhǎng)度,不是 array 會(huì) panic
    Len() int

    // 返回 struct 字段數(shù)量,不是 struct 會(huì) panic
    NumField() int

    // 返回函數(shù)的參數(shù)數(shù)量,不是 func 會(huì) panic
    NumIn() int

    // 返回函數(shù)的返回值數(shù)量,不是 func 會(huì) panic
    NumOut() int

    // 返回函數(shù)第 i 個(gè)返回值的類型,不是 func 會(huì) panic
    Out(i int) Type
}

反射包中 Value 的類型與 Type 不同,它被聲明成了結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體沒有對(duì)外暴露的字段,但是提供了獲取或者寫入數(shù)據(jù)的方法:

type Value struct {
    // 反射出來(lái)此值的類型,rtype 是啥往上看,但可別弄錯(cuò)了,這 typ 是未導(dǎo)出的,從外部調(diào)不到 Type 接口的方法
    typ *rtype

    // 數(shù)據(jù)形式的指針值
    ptr unsafe.Pointer

    // 保存元數(shù)據(jù)
    flag
}
// 前提 v 是一個(gè) func,然后調(diào)用 v,并傳入 in 參數(shù),第一個(gè)參數(shù)是 in[0],第二個(gè)是 in[1],以此類推
func (v Value) Call(in []Value) []Value

// 返回 v 的接口值或者指針
func (v Value) Elem() Value

// 前提 v 是一個(gè) struct,返回第 i 個(gè)字段,這個(gè)主要用于遍歷
func (v Value) Field(i int) Value

// 前提 v 是一個(gè) struct,根據(jù)字段名直接定位返回
func (v Value) FieldByName(name string) Value

// 前提 v 是 Array, Slice, String 之一,返回第 i 個(gè)元素,主要也是用于遍歷,注意不能越界
func (v Value) Index(i int) Value

// 判斷 v 是不是 nil,只有 chan, func, interface, map, pointer, slice 可以用,其他類型會(huì) panic
func (v Value) IsNil() bool

// 判斷 v 是否合法,如果返回 false,那么除了 String() 以外的其他方法調(diào)用都會(huì) panic,事前檢查是必要的
func (v Value) IsValid() bool

// 前提 v 是個(gè) map,返回對(duì)應(yīng) value
func (v Value) MapIndex(key Value)

// 前提 v 是個(gè) map,返回所有 key 組成的一個(gè) slice
func (v Value) MapKeys() []Value

// 前提 v 是個(gè) struct,返回字段個(gè)數(shù)
func (v Value) NumField() int

// 賦值
func (v Value) Set(x Value)

// 類型
func (v Value) Type() Type

// 等等...

實(shí)踐

  • 遍歷一個(gè)結(jié)構(gòu)體的字段以及對(duì)應(yīng)的值

package main

import (
      "fmt"
      "reflect"
  )

type Person struct {
      Name     string
      Sex      string
      Age      int
      PhoneNum string
      School   string
      City     string
  }

func main() {
      p1 := Person{
          Name:     "tom",
          Sex:      "male",
          Age:      10,
          PhoneNum: "1000000",
          School:   "spb-kindergarden",
          City:     "cq",
      }

      rv := reflect.ValueOf(p1)
      rt := reflect.TypeOf(p1)
      if rv.Kind() == reflect.Struct {
          for i := 0; i < rt.NumField(); i++ {
              //按順序遍歷
              fmt.Printf("field:%+v,value:%+v\n", rt.Field(i).Name, rv.Field(i))
          }
      }
  }
  • 若知道字段名,直接去取該字段

rv := reflect.ValueOf(p1)
rt := reflect.TypeOf(p1)
//可以直接取想要的字段
//reflect的type interface,F(xiàn)ieldByName方法會(huì)返回字段信息以及是否有該字段;
if f, ok := rt.FieldByName("Age"); ok {
    fmt.Printf("field:%+v,value:%+v\n", f.Name, rv.FieldByName("Age"))
}

字段信息是一個(gè)結(jié)構(gòu)體,它描述了該字段的下列屬性:

// A StructField describes a single field in a struct.
  type StructField struct {
      // Name is the field name.
      Name string
      // PkgPath is the package path that qualifies a lower case (unexported)
      // field name. It is empty for upper case (exported) field names.
      // See https://golang.org/ref/spec#Uniqueness_of_identifiers
      PkgPath string

      Type      Type      // field type
      Tag       StructTag // field tag string
      Offset    uintptr   // offset within struct, in bytes
      Index     []int     // index sequence for Type.FieldByIndex
      Anonymous bool      // is an embedded field
  }
  • 判斷一個(gè)變量的類型

rv := reflect.ValueOf(p1)
rt := reflect.TypeOf(p1)
fmt.Printf("kind is %+v\n", rt.Kind())
fmt.Printf("kind is %+v\n", rv.Kind())

typevalueKind()方法都可以返回該變量的類型,不過(guò)若取得value后發(fā)現(xiàn)其是一個(gè)零值,那么會(huì)返回KindInvalid

  // Kind returns v's Kind.
  // If v is the zero Value (IsValid returns false),        Kind returns Invalid.
  func (v Value) Kind() Kind {
      return v.kind()
  }

reflectKind一共有27種類型,基本攬括了所有g(shù)olang中的類型

const (
      Invalid Kind = iota
      Bool
      Int
      Int8
      Int16
      Int32
      Int64
      Uint
      Uint8
      Uint16
      Uint32
      Uint64
      Uintptr
      Float32
      Float64
      Complex64
      Complex128
      Array
      Chan
      Func
      Interface
      Map
      Ptr
      Slice
      String
      Struct
      UnsafePointer
  )
  • 獲取tag的值

type TagTest struct {
    Name string `json:"name_json"`
    Age  int    `json:"age_json"`
}

t := TagTest{Name: "tom", Age: 10}
rtt := reflect.TypeOf(t)
//rtv := reflect.ValueOf(t)
for i := 0; i < rtt.NumField(); i++ {
    field := rtt.Field(i)
    if json, ok := field.Tag.Lookup("json"); ok {
        fmt.Printf("tag is %+v, value is %+v\n", json, field.Tag.Get("json"))
    }
}

注意,field.Tag.Lookup()field.Tag.Get()方法都是取tag的值,只不過(guò)Lookup會(huì)用第二個(gè)返回值返回是否存在這個(gè)tag,而Get方法若不存在這個(gè)tag會(huì)返回一個(gè)空字符串

  • 動(dòng)態(tài)調(diào)用方法

*T有方法Add

  type T struct{}

  func (t *T) Add(a, b int) {
      fmt.Printf("a + b is %+v\n", a+b)
  }

動(dòng)態(tài)調(diào)用

funcName := "Add"
typeT := &T{}
a := reflect.ValueOf(1)
b := reflect.ValueOf(2)
in := []reflect.Value{a, b}
reflect.ValueOf(typeT).MethodByName(funcName).Call(in)
  • 動(dòng)態(tài)調(diào)用含返回值的方法

  func (t *T) AddRetErr(a, b int) (int, error) {
      if a+b < 10 {
          return a + b, errors.New("total lt 10")
      }
      return a + b, nil
  }

調(diào)用

      funcName = "AddRetErr"
      ret := reflect.ValueOf(typeT).MethodByName(funcName).Call(in)
      fmt.Printf("ret is %+v\n", ret)
      for i := 0; i < len(ret); i++ {
          fmt.Printf("ret index:%+v, type:%+v, value:%+v\n", i, ret[i].Kind(), ret[i].Interface())
      }

這里的ret[i].Kind(),若非基礎(chǔ)類型,會(huì)得到interface

如果err不是nil

      if v, ok := ret[1].Interface().(error); ok {
          fmt.Printf("v is %+v\n", v)
      }

類型斷言會(huì)成功,可以用這種方式去判斷返回的error是否為空

  • 通過(guò)反射修改值

不是所有的反射值都可以修改。對(duì)于一個(gè)反射值是否可以修改,可以通過(guò)CanSet()進(jìn)行檢查。

要修改值,必須滿足:

  • 可以尋址
  • 可尋址的類型:
    • 指針指向的具體元素
    • slice的元素
    • 結(jié)構(gòu)體指針的字段
    • 數(shù)組指針的元素
1. 指針指向的具體元素

需要三步:

  1. 取地址:v := reflect.ValueOf(&x)
  2. 判斷v.Elem()是否可以設(shè)值
  3. 給v.Elem()設(shè)置具體值
      ta := 10
      vta := reflect.ValueOf(&ta)
      if vta.Elem().CanSet() {
          vta.Elem().Set(reflect.ValueOf(11))
      }
      fmt.Println("cant set")
      fmt.Printf("vta is :%+v\n", vta.Elem())
2. slice中的元素
      ts := []int{1, 2, 3}
      tsV := reflect.ValueOf(ts)
      if tsV.Index(0).CanSet() {
          tsV.Index(0).Set(reflect.ValueOf(10))
      }
      fmt.Printf("ts is %+v\n", ts)
      //輸出:ts is [10 2 3]
3. 結(jié)構(gòu)體指針的字段
      t1 := TagTest{}
      tV := reflect.ValueOf(t)
      
      //結(jié)構(gòu)體指針
      t1V := reflect.ValueOf(&t1)
      
      fmt.Printf("tV:%+v\n", tV)
      for i := 0; i < tV.NumField(); i++ {
          val := tV.Field(i)
          t1V.Elem().Field(i).Set(val)
      }
      fmt.Printf("t1 is %+v\n", t1)
4. 數(shù)組指針的元素
      tsA := [3]int{1, 2, 3}
      tsAv := reflect.ValueOf(&tsA)
      if tsAv.Elem().Index(0).CanSet() {
          tsAv.Elem().Index(0).Set(reflect.ValueOf(10))
      }
      fmt.Printf("tsA is %+v\n", tsA)

注意與slice中元素修改的區(qū)別

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容