狗狼 · 2024年1月5日

反射

常用方法

reflect.Type 和 reflect.Value

interface{}类型变量其具体类型可以使用reflect.Tpye来表示,而其具体值则使用reflect.Value来表示。而reflect.Type和reflect.Value分别提供reflect.TypeOf()和reflect.ValueOf()来获取interface{}的具体类型及具体值。接下来我们结合例子来进行说明

package main

import(
  "fmt"
  "reflect"
)

type order struct{
  ordId int
  customerId int
}

func query(q interface{}) {
  t := reflect.TypeOf(q)
  v := reflect.ValueOf(q)
  fmt.Println("Type ", t)
  fmt.Println("Value ", v)
}

func main(){
  o := order{
    ordId: 456,
    customerId: 56,
  }
 query(o)
}

输出

Type  main.order
Value  {456 56}

可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。

方法名说 明
Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool将值以 bool 类型返回
Bytes() []bytes将值以字节数组 []bytes 类型返回
String() string将值以字符串类型返回
package main
import (
    "fmt"
    "reflect"
)
func main() {
    // 声明整型变量a并赋初值
    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)
}

reflect.Kind

在reflect还有一个比较重要的类型Kind,也是代表类型,看起来和我们前面提到的reflect.Type很相似,其实两者有着很大的差异:

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    t := reflect.TypeOf(q)
    k := t.Kind()
    fmt.Println("Type ", t)
    fmt.Println("Kind ", k)


}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

}

输出:

Type  main.order  
Kind  struct 

通过输出结果我们能够很清楚的看出来reflect.Type和reflect.Kind:Type代表interface{}实际类型main.order;而Kind代表具体类型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        // 底层指针
)
  • Go语言程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。

NumField() 和Field()

NumField()方法获取一个struct所有的fields数量,Field(i int)获取指定第i个field的reflect.Value,结合具体实例:

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    if reflect.ValueOf(q).Kind() == reflect.Struct {
        v := reflect.ValueOf(q)
        fmt.Println("Number of fields", v.NumField())
        for i := 0; i < v.NumField(); i++ {
            fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
        }
    }

}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)
}

输出结果:

Number of fields 2  
Field:0 type:reflect.Value value:456  
Field:1 type:reflect.Value value:56  

Int() 和String()

Int()和String()主要用于从reflect.Value提取对应值作为int64和string类型

package main

import (  
    "fmt"
    "reflect"
)

func main() {  
    a := 56
    x := reflect.ValueOf(a).Int()
    fmt.Printf("type:%T value:%v\n", x, x)
    b := "Naveen"
    y := reflect.ValueOf(b).String()
    fmt.Printf("type:%T value:%v\n", y, y)

}

输出结果

type:int64 value:56  
type:string value:Naveen 

获取指针对应类型

Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,代码如下:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    // 声明一个空结构体
    type cat struct {
    }
    // 创建cat的实例
    ins := &cat{}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(ins)
    // 显示反射类型对象的名称和种类
    fmt.Printf("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())
}
name:'' kind:'ptr'
element name: 'cat', element kind: 'struct'

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

任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象 reflect.Type 的 NumField() 和 Field() 方法获得结构体成员的详细信息。

与成员获取相关的 reflect.Type 的方法如下表所示。

方法说明
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. reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(StructTag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。

结构体标签

  1. 格式 key1:"value1" key2:"value2":结构体标签由一个或多个键值对组成;键与值使用冒号分隔,值用双引号括起来;键值对之间使用一个空格分隔。
  2. 从标签中获取值:
    StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:
    ● func (tag StructTag) Get(key string) string:根据 Tag 中的键获取对应的值,例如key1:"value1" key2:"value2"的 Tag 中,可以传入“key1”获得“value1”。
    ● func (tag StructTag) Lookup(key string) (value string, ok bool):根据 Tag 中的键,查询值是否存在。
  3. 结构体编写必须按照格式来写(切勿画蛇添足):
    编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误

StructField 的结构如下:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路径
    Type      Type       // 字段反射类型对象
    Tag       StructTag  // 字段的结构体标签
    Offset    uintptr    // 字段在结构体中的相对偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否为匿名字段
}

使用示例:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    // 声明一个空结构体
    type cat struct {
        Name string
        // 带有结构体tag的字段
        Type int `json:"type" id:"100"`
    }
    // 创建cat的实例
    ins := cat{Name: "mimi", Type: 1}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(ins)
    // 遍历结构体所有成员
    for i := 0; i < typeOfCat.NumField(); i++ {
        // 获取每个成员的结构体字段类型
        fieldType := typeOfCat.Field(i)
        // 输出成员名和tag
        fmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag)
    }
    // 通过字段名, 找到字段类型信息
    if catType, ok := typeOfCat.FieldByName("Type"); ok {
        // 从tag中取出需要的tag
        fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
    }
}
name: Name  tag: ''
name: Type  tag: 'json:"type" id:"100"'
type 100

Go语言通过反射访问结构体成员的值

方 法备 注
Field(i int) Value根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机
NumField() int返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机
FieldByName(name string) Value根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByIndex(index []int) Value多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByNameFunc(match func(string) bool) Value根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机
package main
import (
    "fmt"
    "reflect"
)
// 定义结构体
type dummy struct {
    a int
    b string
    // 嵌入字段
    float32
    bool
    next *dummy
}
func main() {
    // 值包装结构体
    d := reflect.ValueOf(dummy{
            next: &dummy{},
    })
    // 获取字段数量
    fmt.Println("NumField", d.NumField())
    // 获取索引为2的字段(float32字段)
    floatField := d.Field(2)
    // 输出字段类型
    fmt.Println("Field", floatField.Type())
    // 根据名字查找字段
    fmt.Println("FieldByName(\"b\").Type", d.FieldByName("b").Type())
    // 根据索引查找值中, next字段的int字段的值
    fmt.Println("FieldByIndex([]int{4, 0}).Type()", d.FieldByIndex([]int{4, 0}).Type())
}
NumField 5
Field float32
FieldByName("b").Type string
FieldByIndex([]int{4, 0}).Type() int

代码说明如下:

  • 第 9 行,定义结构体,结构体的每个字段的类型都不一样。
  • 第 24 行,实例化结构体并包装为 reflect.Value 类型,成员中包含一个 *dummy 的实例。
  • 第 29 行,获取结构体的字段数量。
  • 第 32 和 35 行,获取索引为2的字段值(float32 字段),并且打印类型。
  • 第 38 行,根据b字符串,查找到 b 字段的类型。
  • 第 41 行,[]int{4,0} 中的 4 表示,在 dummy 结构中索引值为 4 的成员,也就是 next。next 的类型为 dummy,也是一个结构体,因此使用 []int{4,0} 中的 0 继续在 next 值的基础上索引,结构为 dummy 中索引值为 0 的 a 字段,类型为 int。

判断反射值的空和有效性

方 法说 明
IsNil() bool返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作
IsValid() bool判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。
package main
import (
    "fmt"
    "reflect"
)
func main() {
    // *int的空指针
    var a *int
    fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())
    // nil值
    fmt.Println("nil:", reflect.ValueOf(nil).IsValid())
    // *int类型的空指针
    fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())
    // 实例化一个结构体
    s := struct{}{}
    // 尝试从结构体中查找一个不存在的字段
    fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())
    // 尝试从结构体中查找一个不存在的方法
    fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())
    // 实例化一个map
    m := map[int]int{}
    // 尝试从map中查找一个不存在的键
    fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
var a *int: true
nil: false
(*int)(nil): false
不存在的结构体成员: false
不存在的结构体方法: false
不存在的键: false

通过反射修改值

使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。

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

使用 reflect.Value 修改值的相关方法如下表所示。

方法名备注
Set(x Value)将值设置为传入的反射值对象的值
Setlnt(x int64)使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
SetUint(x uint64)使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机
SetFloat(x float64)使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机
SetBool(x bool)使用 bool 设置值。当值的类型不是 bod 时会发生宕机
SetBytes(x []byte)设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
SetString(x string)设置字符串值。当值的类型不是 string 时会发生宕机

反射修改值必须具备的条件

  1. 可被寻址
package main
import (
    "fmt"
    "reflect"
)
func main() {
    // 声明整型变量a并赋初值
    var a int = 1024
    // 获取变量a的反射值对象(a的地址)
    valueOfA := reflect.ValueOf(&a)
    // 取出a地址的元素(a的值)
    valueOfA = valueOfA.Elem()
    // 修改a的值为1
    valueOfA.SetInt(1)
    // 打印a的值
    fmt.Println(valueOfA.Int())
}
  1. 值被导出
package main
import (
    "reflect"
    "fmt"
)
func main() {
    type dog struct {
            LegCount int
    }
    // 获取dog实例地址的反射值对象
    valueOfDog := reflect.ValueOf(&dog{})
    // 取出dog实例地址的元素
    valueOfDog = valueOfDog.Elem()
    // 获取legCount字段的值
    vLegCount := valueOfDog.FieldByName("LegCount")
    // 尝试设置legCount的值(这里会发生崩溃)
    vLegCount.SetInt(4)
    fmt.Println(vLegCount.Int())
}

反射根据类型信息创建实例

当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。例如 reflect.Type 的类型为 int 时,创建 int 的指针,即*int,代码如下:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var a int
    // 取变量a的反射类型对象
    typeOfA := reflect.TypeOf(a)
    // 根据反射类型对象创建类型实例
    aIns := reflect.New(typeOfA)
    // 输出Value的类型和种类
    fmt.Println(aIns.Type(), aIns.Kind())
}
*int ptr

反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。

下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。

package main
import (
    "fmt"
    "reflect"
)
// 普通函数
func add(a, b int) int {
    return a + b
}
func main() {
    // 将函数包装为反射值对象
    funcValue := reflect.ValueOf(add)
    // 构造函数参数, 传入两个整型值
    paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
    // 反射调用函数
    retList := funcValue.Call(paramList)
    // 获取第一个返回值, 取整数值
    fmt.Println(retList[0].Int())
}

差缺补漏

  1. reflect.Indirect(v)
package main

import (
	"fmt"
	"reflect"
)
func main() {
	type dog struct {
		LegCount int
	}
	// 获取dog实例地址的反射值对象
	valueDog := reflect.ValueOf(&dog{})
  // 根据 reflect.Value 获取 dog 的指针地址
	valueOfDog := reflect.Indirect(valueDog)
	// 获取legCount字段的值
	vLegCount := valueOfDog.FieldByName("LegCount")
	// 尝试设置legCount的值(这里会发生崩溃)
	vLegCount.SetInt(4)
	fmt.Println(vLegCount.Int())
}