脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - GoLang反射机制深入讲解

GoLang反射机制深入讲解

2022-12-24 14:34alwaysrun Golang

这篇文章主要介绍了GoLang反射机制,反射是一种让程序可以在运行时( runtime )检查其数据结构的能力,通过反射可以获取丰富的类型信息

反射

Go语言提供了reflect 包来访问程序的反射信息;定义了两个重要的类型Type和Value:

  • reflect.TypeOf:获取任意值的类型对象(reflect.Type);
  • reflect.ValueOf:获得值的反射值对象(reflect.Value);

反射类型Type

Go语言程序中的类型(Type)指的是系统原生数据类型(如 int、string、bool、float32 等),以及使用 type 关键字定义的类型;而反射种类(Kind)是指对象的归属分类:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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        // 底层指针
)

指针

对指针指向的对象,可通过reflect.Elem() 方法获取这个指针指向的元素类型(等效于对指针类型变量做了一个*操作)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func reflectStruct() {
    type Cat struct {
    }
    aCat := &Cat{}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(aCat)
    fmt.Printf("ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
    // 取类型的元素
    typeOfCat = typeOfCat.Elem()
    fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
    typeOfCat = reflect.TypeOf(*aCat)
    fmt.Printf("*ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
// ptr name:'' kind:'ptr'
// element name: 'Cat', element kind: 'struct'
// *ptr name:'Cat' kind:'struct'

结构体

对结构体对象,获取对象信息后,可通过NumField() 和 Field() 方法获得结构体成员的详细信息。

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

字段信息中含有:

?
1
2
3
4
5
6
7
8
9
type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段在结构体中的路径
    Type      Type       // 字段的反射类型 reflect.Type
    Tag       StructTag  // 字段的结构体标签
    Offset    uintptr    // 字段在结构体中的相对偏移
    Index     []int      // FieldByIndex中的索引顺序
    Anonymous bool       // 是否为匿名字段
}

获取字段的名称与tag:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func reflectStructField()  {
    // 声明一个空结构体
    type Cat struct {
        Name string
        // 带有结构体tag的字段
        Type int `json:"type" id:"100"`
    }
    aCat := Cat{Name: "mimi", Type: 1}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(aCat)
    // 遍历结构体所有成员
    for i := 0; i < typeOfCat.NumField(); i++ {
        fieldType := typeOfCat.Field(i)
        fmt.Printf("Field-%v: name: %v  tag: '%v'\n", i, fieldType.Name, fieldType.Tag)
    }
    // 通过字段名, 找到字段类型信息
    if catType, ok := typeOfCat.FieldByName("Type"); ok {
        fmt.Println("Field Tag: ", catType.Tag.Get("json"), catType.Tag.Get("id"))
    }
}
// Field-0: name: Name  tag: ''
// Field-1: name: Type  tag: 'json:"type" id:"100"'
// Field Tag:  type 100

反射值Value

通过下面几种方法从反射值对象 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
3
4
5
6
7
8
9
10
11
func reflectValue()  {
    var a int = 1024
    // 获取变量a的反射值对象
    valueOfA := reflect.ValueOf(a)
    // 获取interface{}类型的值, 通过类型断言转换
    var getA int = valueOfA.Interface().(int)
    // 获取64位的值, 强制类型转换为int类型
    var getA2 int = int(valueOfA.Int())
    fmt.Println(getA, getA2)
}
// 1024 1024

结构体

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

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

空与有效性判断

反射值对象(reflect.Value)提供一系列方法进行零值和空判定:

方 法 说 明
IsNil() bool 是否为 nil,只对通道、函数、接口、map、指针或切片有效(否则会panic)
IsValid() bool 是否有效,当值本身非法时(不包含任何值,或值为 nil),返回 false

修改值

通过反射修改变量值的前提条件之一:这个值必须可以被寻址,简单地说就是这个变量必须能被修改。结构体成员中,如果字段没有被导出,即便也可以被访问,也不能通过反射修改。

方法名 备 注
Elem() Value 取值指向的元素值(类似于*操作);对指针或接口时发生panic
Addr() Value 对可寻址的值返回其地址(类似于&操作);当值不可寻址时发生panic
CanAddr() bool 表示值是否可寻址
CanSet() bool 返回值能否被修改;要求值可寻址且是导出的字段

修改结构体字段的值(需要结构体地址,与导出字段):

?
1
2
3
4
5
6
7
8
9
10
11
12
func reflectModifyValue()  {
    type Dog struct {
        LegCount int
    }
    // 获取dog实例地址的反射值对象
    valueOfDog := reflect.ValueOf(&Dog{})
    // 取出dog实例地址的元素
    valueOfDog = valueOfDog.Elem()
    vLegCount := valueOfDog.FieldByName("LegCount")
    vLegCount.SetInt(4)
    fmt.Println(vLegCount.Int())
}

函数调用

如果反射值对象(reflect.Value)为函数,可以通过Call()调用:参数使用反射值对象的切片[]reflect.Value构造后传入,返回值通过[]reflect.Value返回。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
func add(a, b int) int {
    return a + b
}
func reflectFunction() {
    // 将函数包装为反射值对象
    funcValue := reflect.ValueOf(add)
    // 构造函数参数, 传入两个整型值
    paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
    // 反射调用函数
    retList := funcValue.Call(paramList)
    // 获取第一个返回值, 取整数值
    fmt.Println(retList[0].Int())
}

反射三定律

官方提供了三条定律来说明反射:

  • 反射可将interface类型变量转换成反射对象;
  • 反射可将反射对象还原成interface对象;
  • 要修改反射对象,其值必须是可写的(反射其指针类型);
?
1
2
3
4
var x float64 = 3.4
v := reflect.ValueOf(x) // v is reflext.Value
var y float64 = v.Interface().(float64)
fmt.Println("value:", y) // 3.4

值类型不能直接修改,可通过传递地址并通过Elem获取后修改:

?
1
2
3
4
var x float64 = 3.4
v := reflect.ValueOf(&x)
v.Elem().SetFloat(7.8)
fmt.Println("x :", v.Elem().Interface())    // 7.8

interface

interface是Go实现抽象的一个非常强大的工具;当向接口赋值时,接口会存储实体的类型信息;反射就是通过接口的类型信息实现的。

interface类型是一种特殊类型,代表方法集合;可存放任何实现了其方法的值(实际存放的是(value,type)对)。reflect包中实现了反射的各种函数:

  • 提取interface的value的方法reflect.ValueOf()->reflect.Value
  • 提取interface的type的方法reflect.TypeOf()->reflect.Type

空interface类型(interface{})的方法集为空,所以可认为任何类型都实现了该接口;因此其可存放任何值。

底层结构

interface底层结构分为iface和eface描述接口,其区别是eface为不包含任何方法的空接口。

iface

iface定义如下:

  • tab 指向一个 itab 实体的指针:表示接口的类型(赋给此接口的实体类型);
  • data 指向接口具体的值:一般而言是一个指向堆内存的指针。
?
1
2
3
4
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

itab结构:

  • _type 字段描述了实体的类型:包括内存对齐方式,大小等;
  • inter 字段则描述了接口的类型;
  • fun 字段放置是实体类中和接口方法对应(实体中其他方法不在此处)的方法地址,以实现接口调用方法的动态分派;一般在每次给接口赋值发生转换时会更新此表。
?
1
2
3
4
5
6
7
8
9
10
type itab struct {
    inter  *interfacetype
    _type  *_type
    link   *itab
    hash   uint32
    bad    bool
    inhash bool
    unused [2]byte
    fun    [1]uintptr
}

iface结构全貌图:

GoLang反射机制深入讲解

eface

eface结构:只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。

?
1
2
3
4
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

到此这篇关于GoLang反射机制深入讲解的文章就介绍到这了,更多相关Go反射内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/alwaysrun/article/details/123024769

延伸 · 阅读

精彩推荐
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710142021-03-08
  • GolangGo语言单链表实现方法

    Go语言单链表实现方法

    这篇文章主要介绍了Go语言单链表实现方法,实例分析了基于Go语言的单链表实现原理与使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    OSC首席键客2992020-04-22
  • Golanggolang gorm的关系关联实现示例

    golang gorm的关系关联实现示例

    这篇文章主要为大家介绍了golang gorm的关系关联实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪...

    Jeff的技术栈6232022-09-20
  • GolangGo语言并发模型的2种编程方案

    Go语言并发模型的2种编程方案

    这篇文章主要介绍了Go语言并发模型的2种编程方案,本文给出共享内存和通过通信的2种解决方案,并给出了实现代码,需要的朋友可以参考下 ...

    junjie1702020-04-09
  • GolangGo 修改map slice array元素值操作

    Go 修改map slice array元素值操作

    这篇文章主要介绍了Go 修改map slice array元素值操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    周伯通的麦田12372021-03-18
  • Golang详解go语言中type关键词的几种使用

    详解go语言中type关键词的几种使用

    这篇文章主要介绍了详解go语言中type的几种使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面...

    tzs9196962021-03-29
  • GolangGo语言实现汉诺塔算法

    Go语言实现汉诺塔算法

    之前的文章,我们给大家分享了不少汉诺塔算法的实现语言,包括C、c++、java、python等,今天我们就来使用go语言来实现一下,需要的小伙伴来参考下吧。...

    脚本之家5102020-04-25
  • GolangGo 大佬良心发现,愿意给 Map 加清除了?

    Go 大佬良心发现,愿意给 Map 加清除了?

    今天要分享的是 Go map 在 NaN 上的一个争议和可能即将出现的 API 增加。...

    脑子进煎鱼了3982022-10-28