欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang中反射的常見用法分享

 更新時(shí)間:2023年01月04日 14:11:21   作者:rubys_  
本篇文章主要為大家詳細(xì)介紹一些Go語言中常見的反射用法,涵蓋了常見的數(shù)據(jù)類型的反射操作。文中的示例代碼講解詳細(xì),感興趣的可以了解一下

在之前的兩篇文章 《深入理解 go reflect - 反射基本原理》《深入理解 go reflect - 要不要傳指針》 中, 我們講解了關(guān)于 go 反射的一些基本原理,以及通過反射對象修改變量的一些注意事項(xiàng)。 本篇文章將介紹一些常見的反射用法,涵蓋了常見的數(shù)據(jù)類型的反射操作。

根據(jù)類型做不同處理

使用反射很常見的一個(gè)場景就是根據(jù)類型做不同處理,比如下面這個(gè)方法,根據(jù)不同的 Kind 返回不同的字符串表示:

func getType(i interface{}) string {
   v := reflect.ValueOf(i)

   switch v.Kind() {
   case reflect.Bool:
      b := "false"
      if v.Bool() {
         b = "true"
      }
      return fmt.Sprintf("bool: %s", b)
   case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      return fmt.Sprintf("int: %d", v.Int())
   case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
      return fmt.Sprintf("uint: %d", v.Uint())
   case reflect.Float32, reflect.Float64:
      return fmt.Sprintf("float: %.1f", v.Float())
   case reflect.String:
      return fmt.Sprintf("string: %s", v.String())
   case reflect.Interface:
      return fmt.Sprintf("interface: %v", v.Interface())
   case reflect.Struct:
      return fmt.Sprintf("struct: %v", v.Interface())
   case reflect.Map:
      return fmt.Sprintf("map: %v", v.Interface())
   case reflect.Slice:
      return fmt.Sprintf("slice: %v", v.Interface())
   case reflect.Array:
      return fmt.Sprintf("array: %v", v.Interface())
   case reflect.Pointer:
      return fmt.Sprintf("pointer: %v", v.Interface())
   case reflect.Chan:
      return fmt.Sprintf("chan: %v", v.Interface())
   default:
      return "unknown"
   }
}

func TestKind(t *testing.T) {
   assert.Equal(t, "int: 1", getType(1))
   assert.Equal(t, "string: 1", getType("1"))
   assert.Equal(t, "bool: true", getType(true))
   assert.Equal(t, "float: 1.0", getType(1.0))

    arr := [3]int{1, 2, 3}
    sli := []int{1, 2, 3}
    assert.Equal(t, "array: [1 2 3]", getType(arr))
    assert.Equal(t, "slice: [1 2 3]", getType(sli))
}

標(biāo)準(zhǔn)庫 json 中的示例

在標(biāo)準(zhǔn)庫 encoding/json 中,也有類似的場景,比如下面這個(gè)方法,根據(jù)不同的 Kind 做不同的處理:

func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
   // ... 其他代碼
   switch t.Kind() {
   case reflect.Bool:
      return boolEncoder
   case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      return intEncoder
   case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
      return uintEncoder
   // ...省略其他 case...
   default:
      return unsupportedTypeEncoder
   }
}

在進(jìn)行 json 編碼的時(shí)候,因?yàn)椴恢纻魅氲膮?shù)是什么類型,所以需要根據(jù)類型做不同的處理,這里就是使用反射來做的。 通過判斷不同的類型,然后返回不同的 encoder。

基本類型的反射

這里說的基本類型是:int*uint*、float*complex*、bool 這種類型。

通過反射修改基本類型的值,需要注意的是,傳入的參數(shù)必須是指針類型,否則會 panic

func TestBaseKind(t *testing.T) {
   // 通過反射修改 int 類型變量的值
   a := 1
   v := reflect.ValueOf(&a)
   v.Elem().SetInt(10)
   assert.Equal(t, 10, a)

   // 通過反射修改 uint16 類型變量的值
   b := uint16(10)
   v1 := reflect.ValueOf(&b)
   v1.Elem().SetUint(20)
   assert.Equal(t, uint16(20), b)

   // 通過反射修改 float32 類型變量的值
   f := float32(10.0)
   v2 := reflect.ValueOf(&f)
   v2.Elem().SetFloat(20.0)
   assert.Equal(t, float32(20.0), f)
}

通過反射修改值的時(shí)候,需要通過 Elem() 方法的返回值來修改。

數(shù)組類型的反射

通過反射修改數(shù)組中元素的值,可以使用 Index 方法取得對應(yīng)下標(biāo)的元素,然后再使用 Set 方法修改值:

func TestArray(t *testing.T) {
   // 通過反射修改數(shù)組元素的值
   arr := [3]int{1, 2, 3}
   v := reflect.ValueOf(&arr)
   // 修改數(shù)組中的第一個(gè)元素
   v.Elem().Index(0).SetInt(10)
   assert.Equal(t, [3]int{10, 2, 3}, arr)
}

chan 反射

我們可以通過反射對象來向 chan 中發(fā)送數(shù)據(jù),也可以從 chan 中接收數(shù)據(jù):

func TestChan(t *testing.T) {
   // 通過反射修改 chan
   ch := make(chan int, 1)
   v := reflect.ValueOf(&ch)
   // 通過反射對象向 chan 發(fā)送數(shù)據(jù)
   v.Elem().Send(reflect.ValueOf(2))
   // 在反射對象外部從 chan 接收數(shù)據(jù)
   assert.Equal(t, 2, <-ch)
}

map 反射

通過反射修改 map 中的值,可以使用 SetMapIndex 方法修改 map 中對應(yīng)的 key

func TestMap(t *testing.T) {
   // 通過反射修改 map 元素的值
   m := map[string]int{"a": 1}
   v := reflect.ValueOf(&m)
   // 修改 a 的 key,修改其值為 2
   v.Elem().SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(2))
   // 外部的 m 可以看到反射對象的修改
   assert.Equal(t, 2, m["a"])
}

迭代反射 map 對象

我們可以通過反射對象的 MapRange 方法來迭代 map 對象:

func TestIterateMap(t *testing.T) {
   // 遍歷 map
   m := map[string]int{"a": 1, "b": 2}
   v := reflect.ValueOf(m)
   // 創(chuàng)建 map 迭代器
   iter := v.MapRange()

   // 迭代 map 的元素
   for iter.Next() {
      // a 1
      // b 2
      fmt.Println(iter.Key(), iter.Value())
   }
}

slice 反射

通過反射修改 slice 中的值,可以使用 Index 方法取得對應(yīng)下標(biāo)的元素,然后再使用 Set* 方法修改值,跟數(shù)組類似:

func TestSlice(t *testing.T) {
   // 通過反射修改 slice 元素的值
   sli := []int{1, 2, 3}
   v := reflect.ValueOf(&sli)
   v.Elem().Index(0).SetInt(10)
   assert.Equal(t, []int{10, 2, 3}, sli)
}

string 反射

對于 string 類型,我們可以通過其反射對象的 String 方法來修改其內(nèi)容:

func TestString(t *testing.T) {
   // 通過反射修改字符串的值
   s := "hello"
   v := reflect.ValueOf(&s)
   v.Elem().SetString("world")
   assert.Equal(t, "world", s)
}

interface/Pointer 反射

對于 interfacePointer 類型,我們可以通過其反射對象的 Elem 方法來修改其內(nèi)容:

func TestPointer(t *testing.T) {
   a := 1

   // 接口類型
   var i interface{} = &a
   v1 := reflect.ValueOf(i)
   v1.Elem().SetInt(10)
   assert.Equal(t, 10, a)

   // 指針類型
   var p = &a
   v2 := reflect.ValueOf(p)
   v2.Elem().SetInt(20)
   assert.Equal(t, 20, a)
}

這兩種類型,我們都需要通過 Elem 方法來先獲取其實(shí)際保存的值,然后再修改其值。

結(jié)構(gòu)體的反射

對于 go 中的結(jié)構(gòu)體,反射系統(tǒng)中為我們提供了很多操作結(jié)構(gòu)體的方法,比如獲取結(jié)構(gòu)體的字段、方法、標(biāo)簽、通過反射對象調(diào)用其方法等。

先假設(shè)我們有如下結(jié)構(gòu)體:

type Person struct {
   Name string
   Age  int

   sex uint8
}

func (p Person) M1() string {
   return "person m1"
}

func (p *Person) M2() string {
   return "person m2"
}

遍歷結(jié)構(gòu)體字段

我們可以通過 NumField 方法來獲取結(jié)構(gòu)體的字段數(shù)量,然后通過 Field 方法來獲取結(jié)構(gòu)體的字段:

func TestStruct1(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   // string Tom
   // int 18
   // uint8 1
   for i := 0; i < v.NumField(); i++ {
      fmt.Println(v.Field(i).Type(), v.Field(i))
   }
}

根據(jù)名稱或索引獲取結(jié)構(gòu)體字段

我們可以根據(jù)結(jié)構(gòu)體字段的名稱或索引來獲取結(jié)構(gòu)體的字段:

func TestStruct2(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   assert.Equal(t, 18, v.Field(1).Interface())
   assert.Equal(t, 18, v.FieldByName("Age").Interface())
   assert.Equal(t, 18, v.FieldByIndex([]int{1}).Interface())
}

修改結(jié)構(gòu)體字段

我們可以通過 Field 方法來獲取結(jié)構(gòu)體的字段,然后再使用 Set* 方法來修改其值:

func TestStruct2(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(&p)

   v.Elem().FieldByName("Name").SetString("Jack")

   assert.Equal(t, "Jack", p.Name)
}

上面因?yàn)?Namestring 類型,所以我們使用 SetString 方法來修改其值,如果是 int 類型,我們可以使用 SetInt 方法來修改其值,依此類推。

結(jié)構(gòu)體方法調(diào)用

通過反射對象來調(diào)用結(jié)構(gòu)體的方法時(shí),需要注意的是,如果我們需要調(diào)用指針接收者的方法,則需要傳遞地址

func TestStruct3(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}

   // 值接收者(receiver)
   v1 := reflect.ValueOf(p)
   assert.Equal(t, 1, v1.NumMethod())
   // 注意:值接收者沒有 M2 方法
   assert.False(t, v1.MethodByName("M2").IsValid())

   // 通過值接收者調(diào)用 M1 方法
   results := v1.MethodByName("M1").Call(nil)
   assert.Len(t, results, 1)
   assert.Equal(t, "person m1", results[0].Interface())

   // 指針接收者(pointer receiver)
   v2 := reflect.ValueOf(&p)
   assert.Equal(t, 2, v2.NumMethod())

   // 通過指針接收者調(diào)用 M1 和 M2 方法
   results = v2.MethodByName("M1").Call(nil)
   assert.Len(t, results, 1)
   assert.Equal(t, "person m1", results[0].Interface())

   results = v2.MethodByName("M2").Call(nil)
   assert.Len(t, results, 1)
   assert.Equal(t, "person m2", results[0].Interface())
}

說明:

  • 結(jié)構(gòu)體參數(shù)是值的時(shí)候,reflect.ValueOf 返回的反射對象只能調(diào)用值接收者的方法,不能調(diào)用指針接收者的方法。
  • 結(jié)構(gòu)體參數(shù)是指針的時(shí)候,reflect.ValueOf 返回的反射對象可以調(diào)用值接收者和指針接收者的方法。
  • 調(diào)用 MethodByName 方法時(shí),如果方法不存在,則返回的反射對象的 IsValid 方法返回 false
  • 調(diào)用 Call 方法時(shí),如果沒有參數(shù),傳 nil 參數(shù)即可。如果方法沒有返回值,則返回的結(jié)果切片為空。
  • 調(diào)用 Call 方法的參數(shù)是 reflect.Value 類型的切片,返回值也是 reflect.Value 類型的切片。

是否實(shí)現(xiàn)接口

對于這個(gè),其實(shí)有一個(gè)更簡單的方法,那就是利用接口斷言:

func TestStrunct4_0(t *testing.T) {
   type TestInterface interface {
      M1() string
   }

   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   // v.Interface() 返回的是 interface{} 類型
   // v.Interface().(TestInterface) 將 interface{} 類型轉(zhuǎn)換為 TestInterface 類型
   v1, ok := v.Interface().(TestInterface)
   assert.True(t, ok)
   assert.Equal(t, "person m1", v1.M1())
}

另外一個(gè)方法是,通過反射對象的 Type 方法獲取類型對象,然后調(diào)用 Implements 方法來判斷是否實(shí)現(xiàn)了某個(gè)接口:

func TestStruct4(t *testing.T) {
   type TestInterface interface {
      M1() string
   }

   var p = Person{Name: "Tom", Age: 18, sex: 1}
   typ := reflect.TypeOf(p)

   typ1 := reflect.TypeOf((*TestInterface)(nil)).Elem()
   assert.True(t, typ.Implements(typ1))
}

結(jié)構(gòu)體的 tag

這在序列化、反序列化、ORM 庫中用得非常多,常見的 validator 庫也是通過 tag 來實(shí)現(xiàn)的。 下面的例子中,通過獲取變量的 Type 就可以獲取其 tag 了:

type Person1 struct {
   Name string `json:"name"`
}

func TestStruct5(t *testing.T) {
   var p = Person1{Name: "Tom"}
   typ := reflect.TypeOf(p)
   tag := typ.Field(0).Tag

   assert.Equal(t, "name", tag.Get("json"))
}

修改結(jié)構(gòu)體未導(dǎo)字段

我們知道,結(jié)構(gòu)體的字段如果首字母小寫,則是未導(dǎo)出的,不能被外部包訪問。但是我們可以通過反射修改它:

func TestStruct6(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(&p)

   // 下面這樣寫會報(bào)錯:
   // panic: reflect: reflect.Value.SetInt using value obtained using unexported field
   // v.Elem().FieldByName("sex").SetInt(0)
   ft := v.Elem().FieldByName("sex")

   sexV := reflect.NewAt(ft.Type(), unsafe.Pointer(ft.UnsafeAddr())).Elem()

   assert.Equal(t, 1, p.sex)           // 修改前是 1
   sexV.Set(reflect.ValueOf(uint8(0))) // 將 sex 字段修改為 0
   assert.Equal(t, 0, p.sex)           // 修改后是 0
}

這里通過 NewAt 方法針對 sex 這個(gè)未導(dǎo)出的字段創(chuàng)建了一個(gè)指針,然后我們就可以通過這個(gè)指針來修改 sex 字段了。

方法的反射

這里說的方法包括函數(shù)和結(jié)構(gòu)體的方法。

入?yún)⒑头祷刂?/h3>

reflect 包中提供了 InOut 方法來獲取方法的入?yún)⒑头祷刂担?/p>

func (p Person) Test(a int, b string) int {
   return a
}

func TestMethod(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   m := v.MethodByName("Test")
   // 參數(shù)個(gè)數(shù)為 2
   assert.Equal(t, 2, m.Type().NumIn())
   // 返回值個(gè)數(shù)為 1
   assert.Equal(t, 1, m.Type().NumOut())

   // In(0) 是第一個(gè)參數(shù),In(1) 是第二個(gè)參數(shù)
   arg1 := m.Type().In(0)
   assert.Equal(t, "int", arg1.Name())
   arg2 := m.Type().In(1)
   assert.Equal(t, "string", arg2.Name())

   // Out(0) 是第一個(gè)返回值
   ret0 := m.Type().Out(0)
   assert.Equal(t, "int", ret0.Name())
}

說明:

  • InOut 方法返回的是 reflect.Type 類型,可以通過 Name 方法獲取類型名稱。
  • NumInNumOut 方法返回的是參數(shù)和返回值的個(gè)數(shù)。
  • reflect.Value 類型的 MethodByName 方法可以獲取結(jié)構(gòu)體的方法。

通過反射調(diào)用方法

reflect.Value 中對于方法類型的反射對象,有一個(gè) Call 方法,可以通過它來調(diào)用方法:

func TestMethod2(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   // 通過反射調(diào)用 Test 方法
   m := v.MethodByName("Test")
   arg1 := reflect.ValueOf(1)
   arg2 := reflect.ValueOf("hello")
   args := []reflect.Value{arg1, arg2}

   rets := m.Call(args)
   assert.Len(t, rets, 1)
   assert.Equal(t, 1, rets[0].Interface())
}

說明:

  • Call 方法的參數(shù)是 []reflect.Value 類型,需要將參數(shù)轉(zhuǎn)換為 reflect.Value 類型。
  • Call 方法的返回值也是 []reflect.Value 類型。
  • reflect.Value 類型的 MethodByName 方法可以獲取結(jié)構(gòu)體的方法的反射對象。
  • 通過方法的反射對象的 Call 方法可以實(shí)現(xiàn)調(diào)用方法。

總結(jié)

  • 通過 reflect.Kind 可以判斷反射對象的類型,Kind 涵蓋了 go 中所有的基本類型,所以反射的時(shí)候判斷 Kind 就足夠了。
  • 如果要獲取反射對象的值,需要傳遞指針給 reflect.Value
  • 可以往 chan 的反射對象中發(fā)送數(shù)據(jù),也可以從 chan 的反射對象中接收數(shù)據(jù)。
  • SetMapIndex 方法可以修改 map 中的元素。MapRange 方法可以獲取 map 的迭代器。
  • 可以通過 Index 方法獲取 slice 的元素,也可以通過 SetIndex 方法修改 slice 的元素。
  • 可以通過 SetString 方法修改 string 的值。
  • 對于 interfacePointer 類型的反射對象,可以通過 Elem 方法獲取它們的值,同時(shí)也只有通過 Elem 獲取到的反射對象能調(diào)用 Set* 方法來修改其指向的對象。
  • reflect 包中提供了很多操作結(jié)構(gòu)體的功能:如獲取結(jié)構(gòu)體的字段、獲取結(jié)構(gòu)體的方法、調(diào)用結(jié)構(gòu)體的方法等。我們使用一些類庫的時(shí)候,會需要通過結(jié)構(gòu)體的 tag 來設(shè)置一些元信息,這些信息只有通過反射才能獲取。
  • 我們可以通過 NewAt 來創(chuàng)建一個(gè)指向結(jié)構(gòu)體未導(dǎo)出字段的反射對象,這樣就可以修改結(jié)構(gòu)體的未導(dǎo)出字段了。
  • 對于函數(shù)和方法,go 的反射系統(tǒng)也提供了很多功能,如獲取參數(shù)和返回值信息、使用 Call 來調(diào)用函數(shù)和方法等。

到此這篇關(guān)于Golang中反射的常見用法分享的文章就介紹到這了,更多相關(guān)Golang反射內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang單元測試與斷言編寫流程詳解

    Golang單元測試與斷言編寫流程詳解

    這篇文章主要介紹了Golang單元測試與斷言編寫流程,單元測試也是一個(gè)很重要的事情。單元測試是指在開發(fā)中,對一個(gè)函數(shù)或模塊的測試。其強(qiáng)調(diào)的是對單元進(jìn)行測試
    2022-12-12
  • go-micro微服務(wù)domain層開發(fā)示例詳解

    go-micro微服務(wù)domain層開發(fā)示例詳解

    這篇文章主要為大家介紹了go-micro微服務(wù)domain層開發(fā)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • golang中包無法引入問題解決

    golang中包無法引入問題解決

    本文主要介紹了golang中包無法引入問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom

    golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom

    這篇文章主要為大家介紹了golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • go語言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù)

    go語言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù)

    這篇文章主要為大家介紹了go語言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Nunu快速構(gòu)建高效可靠Go應(yīng)用腳手架使用詳解

    Nunu快速構(gòu)建高效可靠Go應(yīng)用腳手架使用詳解

    這篇文章主要為大家介紹了如何使用Nunu快速構(gòu)建高效可靠Go應(yīng)用腳手架詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • 詳解Go語言實(shí)現(xiàn)線性查找算法和二分查找算法

    詳解Go語言實(shí)現(xiàn)線性查找算法和二分查找算法

    線性查找又稱順序查找,它是查找算法中最簡單的一種。二分查找,也稱折半查找,相比于線性查找,它是一種效率較高的算法。本文將用Go語言實(shí)現(xiàn)這兩個(gè)查找算法,需要的可以了解一下
    2022-12-12
  • Golang數(shù)組的傳遞詳解

    Golang數(shù)組的傳遞詳解

    今天小編就為大家分享一篇關(guān)于Golang數(shù)組的傳遞詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • GO語言實(shí)現(xiàn)文件上傳的示例代碼

    GO語言實(shí)現(xiàn)文件上傳的示例代碼

    這篇文章主要分享一下golang實(shí)現(xiàn)文件上傳的流程和具體代碼,供大家參考,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助
    2022-08-08
  • Go 語言結(jié)構(gòu)實(shí)例分析

    Go 語言結(jié)構(gòu)實(shí)例分析

    在本篇文章里小編給大家整理的是一篇關(guān)于Go 語言結(jié)構(gòu)實(shí)例分析的相關(guān)知識點(diǎn),有興趣的朋友們可以學(xué)習(xí)下。
    2021-07-07

最新評論