什么是反射
在 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)
}
}