第十章 反射

什么是反射

反射是指在程序运行期间对程序本身进行访问和修改的能力。

1.1 反射的类型对象(reflect.Type)

在Go程序中,使用reflect.Typeof()函数可以获得任意值得类型对象,程序通过类型对象可以访问任意值得类型信息。

package main

import (
  "fmt"
  "reflect"
)

func main()  {
  var a int
  typeOfA := reflect.TypeOf(a)
  fmt.Println(typeOfA.Name(),typeOfA.Kind())
}

代码输出如下:

int int

通过reflect.TypeOf()取得变量a的类型对象typeOfA,类型为reflect.Type()。通过typeOfA类型对象的成员函数,可以分别获取到typeOfA变量得类型名称为int,种类名称(kind)为int。

1.1.1 理解反射的类型(Type)与种类(kind)

(1) 反射种类(kind)的定义

Go程序中的类型(Type)指的是系统原生数据类型,如int、string、bool、float32等类型,以及使用type关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用type A struct{}定义结构体时,A就是struct{}的类型。
种类(kind)指的是对象归属的品种,在reflect包的定义如下:

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        // 底层指针
)

1.1.2从类型对象中获取类型名称和种类

  获取类型名称type使用的是reflect.Type中的Name()方法,返回表示类型名称的字符串。
  类型归属种类kind使用的是reflect.Type中的Kind()方法,返回是reflect.kind类型中的常量。

package main

import (
    "fmt"
    "reflect"
)

type Enum int

const (
    Zero Enum = 0
)

func main() {
    type cat struct {
    }
    typeOfCat := reflect.TypeOf(cat{})

    fmt.Println(typeOfCat.Name(), typeOfCat.Kind())

    typeOfA := reflect.TypeOf(Zero)

    fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

代码输出如下:

cat struct
Enum int

1.1.3 指针与指针指向的元素

对指针获取反射对象时,可以通过reflect.Elem()方法获取这个指针指向的元素类型。

package main

import (
    "fmt"
    "reflect"
)

func main()  {
    type cat struct {

    }
    ins := new(cat)
    typeOfCat := reflect.TypeOf(ins)
    fmt.Printf("name:'%v' kind:'%v'\n",typeOfCat.Name(), typeOfCat.Kind())
    typeOfCat = typeOfCat.Elem()
    fmt.Printf("name:'%v' kind:'%v'\n",typeOfCat.Name(), typeOfCat.Kind())
}

代码输出如下:

name:'' kind:'ptr'
name:'cat' kind:'struct'

1.1.4使用反射获取结构体的成员类型

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。
NumField() int 返回结构体成员字段数量。
FieldByName(name string)(StructField,bool) 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时返回false。
FieldByIndex(index []int) StructField 多层成员访问时,根据[]int提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值
FieldByNameFunc(match func(string) bool)(StructField, bool) 根据匹配函数匹配需要的字段

获取成员反射信息

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type cat struct {
        Name string
        Type int `json:"type" id:"100"`
    }

    ins := cat{Name: "mimi", Type: 1}

    typeOfCat := reflect.TypeOf(ins)
    // 遍历结构体所有成员
    for i := 0; i < typeOfCat.NumField(); i++ {
        // 获取一个成员
        fieldType := typeOfCat.Field(i)
        //获取成员的名字和标签
        fmt.Printf("name:'%v' tag:'%v'\n", fieldType.Name, fieldType.Tag)
    }
    // 通过字段名找到字段类型信息
    if catType, ok := typeOfCat.FieldByName("Type"); ok {
        fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
    }
}

输出结果:

name:'Name' tag:''
name:'Type' tag:'json:"type" id:"100"'
type 100

1.2反射的值对象(reflect.Value)

Go语言中使用reflect.Value获取和设置变量得值。使用reflect.ValueOf()函数获得值得反射值对象(reflect.Value)。

1.2.1 从反射值对象(reflect.Value)中获取值得方法

通过以下方法从反射值对象中获取原始值。

方法名 说明
Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool 将值以 bool 类型返回
Bytes() []bytes 将值以字节数组 []bytes 类型返回
String() string 将值以字符串类型返回
1.2.2获取值对象的例子——使用反射访问结构体的成员字段的值

反射值对象(reflect.Value)提供了对结构体访问的方法,通过这些方法可以完成对结构体任意值得访问。

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。
NumField() int 返回结构体成员字段数量。
FieldByName(name string)(StructField,bool) 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时返回false。
FieldByIndex(index []int) StructField 多层成员访问时,根据[]int提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值
FieldByNameFunc(match func(string) bool)(StructField, bool) 根据匹配函数匹配需要的字段
(1)访问变量的值
package main

import (
    "fmt"
    "reflect"
)

type dummy struct {
    a    int
    b    string
    next *dummy
}

func main() {
    v := reflect.ValueOf(dummy{
        a:    3,
        b:    "hello",
        next: &dummy{},
    })
    // 获取字段数量
    numFields := v.NumField()
    fmt.Println("NumField", numFields)

    //获取索引为1的字段
    fieldB := v.Field(1)
    //获取值
    getFieldB := fieldB.String()
    fmt.Println("fieldB value:", getFieldB)
    //根据名字查找字段
    fmt.Println("FieldByName:", v.FieldByName("b").Type())
    //根据索引查找值
    fmt.Println("FieldByIndex:", v.FieldByIndex([]int{0}).Type())
    //根据索引查找值,[]int{2,1}中的2表示,在dummy结构中索引值为2的成员,也就next。next的类型为dummy,也是一个结构体,因此使用[]int{2,1}
    //中的1继续在next的基础上索引,结构为dummy中的索引值为1的b字段,类型为string。
    fmt.Println("FieldByIndex:", v.FieldByIndex([]int{2, 1}).Type())
}

输出结果:

NumField 3
fieldB value: hello
FieldByName: string
FieldByIndex: int
FieldByIndex: string
(2)修改变量的值

修改值得相关的方法

Set(x Value) 将值设置为传入的反射值对象的值
SetInt(x int64) 使用int64设置值。
SetUint(x uint64) 使用int64设置值。
SetFloat(x float64) 使用float64设置值。
SetBool(x bool) 使用bool设置值。
SetString(x string) 设置字符串值。

注意:
1.值可以被修改的条件之一:可被寻址
2.值可以被修改的条件之二:可被导出

package main

import (
    "fmt"
    "reflect"
)

type dog struct {
    LegCount int
}

func main() {
    dog := &dog{}
    valueOfDog := reflect.ValueOf(dog)
    //
    valueOfDog = valueOfDog.Elem()
    //
    vLegCount := valueOfDog.FieldByName("LegCount")

    vLegCount.SetInt(4)
    //
    fmt.Println(dog.LegCount)
}

输出结果:

4
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 什么是反射? 为何需要检查变量,确定变量的类型? reflect 包reflect.Type 和 reflect....
    酷走天涯阅读 534评论 0 0
  • 目录 通过反射获取类型信息[https://www.cnblogs.com/itbsl/p/10551880.ht...
    gurlan阅读 716评论 0 0
  • 什么情况下用到反射 有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也...
    learninginto阅读 658评论 0 6
  • 参考:http://c.biancheng.net/view/4407.html 关键点 通过关键词汇,实现快速理...
    码二哥阅读 622评论 1 4
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    余生动听阅读 10,805评论 0 11

友情链接更多精彩内容