第十九章:Go語言反射

golang-gopher.png

1. 概述

Go語言提供了一種機制,能夠在運行時更新變量和檢查它們的值、調(diào)用它們的方法和它們支持的內(nèi)在操作,而不需要在編譯時就知道這些變量的具體類型。這種機制被稱為反射。反射也可以讓我們將類型本身作為第一類的值類型處理。

2. 反射類型對象

使用reflect.TypeOf() 函數(shù)獲取任意變量的類型對象 reflect.Type

**函數(shù)的源碼如下 : **

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
  eface := *(*emptyInterface)(unsafe.Pointer(&i))
  return toType(eface.typ)
}

通過獲取的類型對象就能訪問原變量的類型信息

在類型信息中我們需要知道 類型(Type)種類(Kind)的區(qū)別

類型 Type : 通常指的是系統(tǒng)中的原生數(shù)據(jù)類型和使用type 關(guān)鍵字定義的類型,通過類型對象 reflect.Type中的Name() 方法獲取

種類Kind : 指定的對象的根本種類,是更高一層的概括,通過reflect.Type 中的 Kind() 函數(shù)獲取

  • Type的值和Kind的值可能相同,也可能不相同
package main

import (
    "fmt"
    "reflect"
)

type char string
type dogs struct {
}

func main() {
    var c char
    Str := "golang go.."
    // 獲取C的類型對象
    TypeOfC := reflect.TypeOf(c)
    // Name() 獲取類型
    // Kind() 獲取種類
    fmt.Println("Type = ", TypeOfC.Name(), "Kind = ", TypeOfC.Kind()) // Type =  char Kind =  string
    Golden := dogs{}
    TypeOfGolden := reflect.TypeOf(Golden)
    fmt.Println("Type = ", TypeOfGolden.Name(), "Kind = ", TypeOfGolden.Kind()) // Type =  char Kind =  string
    TypeOfStr := reflect.TypeOf(Str)
    fmt.Println("Type = ", TypeOfStr.Name(), "Kind = ", TypeOfStr.Kind()) //Type =  string Kind =  string

    huntaway := &dogs{}   // 指針變量
    TypeOfHuntaway := reflect.TypeOf(huntaway)
    // Go語言中所有的指針變量種類都是 `ptr`
    // 指針變量的類型是此時是空
    fmt.Println("Type = ", TypeOfHuntaway.Name(), "Kind = ", TypeOfHuntaway.Kind()) // Type =   Kind =  ptr
    // 對指針獲取反射對象時,可以通過 reflect.Elem() 方法獲取這個指針指向的元素類
    TypeOfHuntaway = TypeOfHuntaway.Elem()
    fmt.Println("Type = ", TypeOfHuntaway.Name(), "Kind = ", TypeOfHuntaway.Kind()) // Type =  dogs Kind =  struct 
}

go run main.go

Type =  char Kind =  string
Type =  dogs Kind =  struct
Type =  string Kind =  string
Type =   Kind =  ptr
Type =  dogs Kind =  struct

當(dāng)一個變量是結(jié)構(gòu)體實例的時候,怎么通過反射獲取類型信息呢?

通過 reflect 包中reflect.typeField() FieldByIndex FieldByName FieldByNameFunc 方法獲取的 StructField 結(jié)構(gòu)體有些什么內(nèi)容呢?

// 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  
}
package main

import (
    "fmt"
    "reflect"
)

type dogs struct {
    Name  string `json:"name"`
    Age int8
    T int `json:"t" id:"99"`
}
func main(){
    // 創(chuàng)建實例
    Hachiko := dogs{Name:"Hachiko",Age:int8(2),T:66}
    //獲取反射對象實例
    typeD := reflect.TypeOf(Hachiko)
    // NumField()函數(shù),返回結(jié)構(gòu)體成員的數(shù)量
    for i:=0;i<typeD.NumField();i++{
        // 獲取結(jié)構(gòu)體成員的類型
        // Field()函數(shù) 根據(jù)索引返回結(jié)構(gòu)體對應(yīng)field的信息
        typeOfField := typeD.Field(i)
        // 輸出成員字段名稱和tag(標(biāo)簽信息),成員類型
        fmt.Printf("%s , %v,%s\n",typeOfField.Name,typeOfField.Tag,typeOfField.Type)
    }
    // 通過結(jié)構(gòu)體字段名獲取其類型信息
    // FieldByName()函數(shù),根據(jù)字段名返回字段信息
    if fieldType,ok := typeD.FieldByName("T");ok{
        fmt.Println(fieldType.Tag.Get("json"),fieldType.Tag.Get("id"))
    }
}

go run main.go

Name , json:"name",string
Age , ,int8
T , json:"t" id:"99",int
t 99

3. 反射的值對象

反射可以動態(tài)的獲取或者設(shè)置變量的值
Go語言中使用reflect.Value獲取和設(shè)置變量的值

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a int = 99
    fmt.Printf("a ==> %T,%v\n", a, a)
    // 使用reflect.ValueOf()函數(shù)獲取反射值對象
    valueOfA := reflect.ValueOf(a)
    // 獲取的反射值對象,再通過值對象的Interface()方法獲取原值
    var b int = valueOfA.Interface().(int)
    fmt.Printf("b ==> %T,%v\n", b, b)
    // 反射對象的Int()方法獲取int64類型值,然后強制轉(zhuǎn)換成int32位
    var c int32 = int32(valueOfA.Int())
    fmt.Printf("c ==> %T,%v\n", c, c)
}

go run main.go

a ==> int,99
b ==> int,99
c ==> int32,99
package main

import (
    "fmt"
    "reflect"
)

type demo struct {
    a int
    b string
    c bool
    float64
    d [5]int
}

func (d *demo) dF1() {
    fmt.Println(d.a)
}
func (d *demo) dF2() {
    fmt.Println(d.b)
}

func main() {
    t := demo{99, "golang", true, 98.90, [5]int{1, 2, 3, 5, 6}}
    // 獲取值對象
    valueOfT := reflect.ValueOf(t)
    // NumField()是獲取字段數(shù)量
    fmt.Println(valueOfT.NumField()) // 5
    // 獲取索引為1的字段
    fieldOf1 := valueOfT.Field(1)
    // 打印該值對象的類型
    fmt.Println(fieldOf1.Type()) // string
    // 通過字段名查找
    fieldOfd := valueOfT.FieldByName("d")
    fmt.Println(fieldOfd.Type()) // [5]int
}

go run main.go

5
string
[5]int

4. 反射修改值

reflect.Value 也提供了修改版值的方法

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var age int8 = 99
    // 獲取一個值對象
    valueOfAge := reflect.ValueOf(&age)
    // Elem() 對可尋值的元素獲取它的值
    // Addr() 對可尋址的元素獲取它地址
    // CanSet() bool 返回元素(值對象)是否能被設(shè)置
    // CanAddr() bool 返回元素(值對象)是否能被尋址
    valueOfAge = valueOfAge.Elem()
    valueOfAgeAddr := valueOfAge.Addr()
    fmt.Println(valueOfAge) // 99
    fmt.Println(valueOfAgeAddr) // 0xc000054080
    fmt.Println(valueOfAge.CanSet()) // true
    fmt.Println(valueOfAge.CanAddr()) // true
    // SetInt() 使用int64設(shè)置值
    // SetUint() 使用uint64設(shè)置值
    // SetFloat() 使用float64設(shè)置值
    // SetBool() 使用bool設(shè)置值
    // SetBytes() 設(shè)置字節(jié)數(shù)組[]bytes值
    // SetString 設(shè)置字符串值
    valueOfAge.SetInt(1)
    fmt.Printf("age type= %T, value= %v",age,age)
}

go run main.go

99
0xc000054080
true
true
age type= int8, value= 15

通過類型創(chuàng)建類型實例

package main

import (
    "fmt"
    "reflect"
)

func main(){
    var i  int = 99
    // 獲取反射類型對象
    typeOfI := reflect.TypeOf(i)
    // 根據(jù)反射類型對象創(chuàng)建類型實例
    newI := reflect.New(typeOfI)
    // 答應(yīng)類型和種類
    fmt.Println(newI.Type(),newI.Kind()) //*int ptr
}


5. 綜合Demo

package main

import (
    "fmt"
    "log"
    "reflect"
)

type Person struct {
    Name string `json:"name"`
    Age int  `json:"age"`
    Sex string  `json:"sex"`
}
func (p Person) PersonSet(name string,age int,sex string){
    p.Name = name
    p.Age = age
    p.Sex = sex
    fmt.Println(p)
}
func (p Person) ShowPerson(){
    fmt.Println(p)
}
func reflectOfStruct(a interface{}){
    // 獲取reflect.Type類型
    typeObj := reflect.TypeOf(a)
    // 獲取reflect.Value類型
    valueObj := reflect.ValueOf(a)
    // 獲取Kind類別,下面兩種方法都能獲取
    KindType := typeObj.Kind()
    //KindValue := valueObj.Kind()
    //fmt.Println(KindType)
    //fmt.Println(KindValue)
    if KindType != reflect.Struct{
        log.Fatal("Kind is not error")
        return
    }
    // 獲取字段數(shù)量
    fieldsNum := valueObj.NumField()
    for i:=0;i<fieldsNum;i++{
        fmt.Printf("field %d %v\n",i,valueObj.Field(i))
        // 獲取指定的標(biāo)簽值
        tagValue := typeObj.Field(i).Tag.Get("json")
        if tagValue != ""{
            fmt.Printf("field %d tag = %v\n",i,tagValue)
        }
    }
    // 獲取方法數(shù)量
    MethodNum := valueObj.NumMethod()
    fmt.Printf("has %d methods\n",MethodNum)
    // 調(diào)用第一個方法
    valueObj.Method(1).Call(nil)
    // 對有參數(shù)的方法調(diào)用
    var params []reflect.Value
    params = append(params,reflect.ValueOf("lisi"))
    params = append(params,reflect.ValueOf(88))
    params = append(params,reflect.ValueOf("man"))
    // 傳遞參數(shù),調(diào)用指定方法名的方法
    valueObj.MethodByName("PersonSet").Call(params)
}
func main() {
    var a Person = Person{
        Name:"zhangsan",
        Age:99,
    }
    // 調(diào)用函數(shù)
    reflectOfStruct(a)
}

go run main.go

field 0 zhangsan
field 0 tag = name
field 1 99
field 1 tag = age
field 2 
field 2 tag = sex
has 2 methods
{zhangsan 99 }
{lisi 88 man}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • [TOC] Golang的反射reflect深入理解和示例 【記錄于2018年2月】 編程語言中反射的概念 在計算...
    AllenWu閱讀 918評論 1 14
  • 此篇文章引自https://juejin.im/post/5a75a4fb5188257a82110544#hea...
    北春南秋閱讀 3,333評論 1 0
  • fmt格式化字符串 格式:%[旗標(biāo)][寬度][.精度][arg索引]動詞旗標(biāo)有以下幾種:+: 對于數(shù)值類型總是輸出...
    皮皮v閱讀 1,223評論 0 3
  • Go語言做Web編程非常方便,并且在開發(fā)效率和程序運行效率方面都非常優(yōu)秀。相比于Java,其最大的優(yōu)勢就是簡便易用...
    暗黑破壞球嘿哈閱讀 9,175評論 6 66
  • 1)Export單獨導(dǎo)出IPA文件至本地端 2)通過ApplicationLoader工具添加當(dāng)前應(yīng)用的spa文件...
    衹氏閱讀 121評論 0 0

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