什么是反射
在 Go 语言中,反射(Reflection)是指在程序运行时动态地获取变量的类型信息和值,并对其进行操作的能力。通过反射,我们可以在不知道具体类型的情况下,对变量进行类型检查、调用方法、获取和设置字段值等操作。
反射的实现
反射
go 语言的反射是通过接口实现的。而 go 中的接口变量其实是用 iface 和 eface 这两个结构体来表示的:
iface 表示某一个具体的接口(含有方法的接口)
eface 表示一个空接口(interface{})
go底层的类型信息是使用 _type 结构体来存储的。
type iface struct {
    tab  *itab // 方法表
    data unsafe.Pointer
}
type eface struct {
    _type *_type
    data  unsafe.Pointer
}
type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}
interfacetype的mhdr存放函数,itab._type存放类型
一个接口中包含了变量的类型信息和类型的数据。因此,我们才可以通过反射来获取到变量的类型信息,以及变量的数据信息。
在包 reflect 中实现了运行时的反射能力,反射包中有两对非常重要的函数和类型,它们与函数是一一对应的关系。
两个函数
reflect.TypeOf 能获取类型信息
reflect.ValueOf 能获取数据的运行时表示两个类型
reflect.Type 和 reflect.Value,reflect.Type 是一个接口而 reflect.Value 是一个结构体。
type Type interface {
    // Methods applicable to all types.
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int
    Name() string
    Kind() Kind
    // Size returns the number of bytes needed to store
    // a value of the given type; it is analogous to unsafe.Sizeof.
    Size() uintptr
    ……
    ……
}
type Value struct {
    //Value的类型
    typ *rtype
    ptr unsafe.Pointer
    flag
}
三大法则
第一法则(从 interface{} 变量可以反射出反射对象)
基本类型 int 会转换成 interface{} 类型,这也就是为什么第一条法则是从接口到反射对象。第二法则(从反射对象可以获取 interface{} 变量)
将反射对象还原成接口类型的变量,采用 reflect.Value.Interface,注意需要显示转换
v := reflect.ValueOf(1)
v.Interface().(int)
- 第三法则(要修改反射对象,其值必须可设置)
 
func main() {
    i := 1
    v := reflect.ValueOf(&i)
    v.Elem().SetInt(10)
    fmt.Println(i)
}
调用 reflect.ValueOf 获取变量指针;
调用 reflect.Value.Elem 获取指针指向的变量;
调用 reflect.Value.SetInt 更新变量的值:
使用场景
- 处理未知类型的数据
 - 实现通用的数据处理逻辑
 - 实现序列化和反序列化
 
注意
反射的使用会带来一定的性能损耗,因此在性能要求较高的场景下,应慎重使用反射。同时,反射的代码可读性较低,容易引入错误,因此应尽量避免滥用反射。
方法使用
判定及获取元素的相关方法
使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。
反射值对象的判定及获取元素的方法
| 方法名 | 备注 | 返回值 | 
|---|---|---|
| Elem() | 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回 nil 的 Value | Value | 
| Addr() | 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机 | Value | 
| CanAddr() | 表示值是否可寻址 | bool | 
| CanSet() | 返回值能否被修改。要求值可寻址且是导出的字段 | bool | 
使用 reflect.Value 修改值的相关方法如下表所示。
反射值对象修改值的方法
| 方法名 | 备注 | 
|---|---|
| Set(x Value) | 将值设置为传入的反射值对象的值。 | 
| SetInt(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 设置值。当值的类型不是 bool 时会发生宕机。 | 
| SetBytes(x []byte) | 设置字节数组 []byte 值。当值的类型不是 []byte 时会发生宕机。 | 
| SetString(x string) | 设置字符串值。当值的类型不是 string 时会发生宕机。 | 
基本使用
package main
import (
    "fmt"
    "reflect"
)
func main() {
    var a int = 3
    // 取变量a的反射类型对象
    typeOfA := reflect.TypeOf(a)
    // 根据反射类型对象创建类型实例
    aIns := reflect.New(typeOfA)
    // 输出Value的类型和种类
    fmt.Println(aIns.Type(), aIns.Kind())
}
通过反射调用函数
type node struct {
    name string
    val  string
}
func (t node) Print() {
    fmt.Printf("output node val is %s\n", t.val)
}
func main() {
    var obj = node{
        name: "根",
        val:  "1",
    }
    typ := reflect.TypeOf(obj)
    fmt.Println(typ.Kind())
    va := reflect.ValueOf(obj)
    method := va.MethodByName("Print") //注意此处要大写开头才能导出字段
    // 判断方法是否存在
    if method.IsValid() {
        // 调用方法,传入空参数
        method.Call(nil)
    }
}