反射reflect

2021/09/10 go探索发现 共 1941 字,约 6 分钟

反射

reflect 实现了运行时的反射能力,能够让程序操作不同类型的对象。反射包中有两对非常重要的函数和类型,两个函数分别是:

  • reflect.TypeOf 能获取类型信息;
  • reflect.ValueOf 能获取数据的运行时表示;

接口和反射对象的双向转换

reflect.TypeOfreflect.ValueOf实现了参数中获取的接口类型转换成反射对象Reflection Object.

从反射对象到接口值的过程是从接口值到反射对象的镜面过程,两个过程都需要经历两次转换:

  • 从接口值到反射对象:
    • 从基本类型到接口类型的类型转换;
    • 从接口类型到反射对象的转换;
  • 从反射对象到接口值:
    • 反射对象转换成接口类型;
    • 通过显式类型转换变成原始类型;

当然不是所有的变量都需要类型转换这一过程。如果变量本身就是 interface{} 类型的,那么它不需要类型转换,因为类型转换这一过程一般都是隐式的,所以我不太需要关心它,只有在我们需要将反射对象转换回基本类型时才需要显式的转换操作。

原理

在运行时想要动态访问类型的值,必然应用程序存储了所有用到的类型信息。”reflect” 库提供了一套供开发者使用的访问接口。

Go 中反射的基础是接口和类型,Go 很巧妙的借助了对象到接口的转换时使用的数据结构,先将对象传递给内部的空接口,即将类型转换成空接口 emptyInterface(数据结构同 eface 一致)。

然后反射再基于这个 emptyInterface 来访问和操作实例对象的值和类型。

eflection Object

  • reflect.Type是接口类型
  • reflect.Value是结构体类型

转换

  • TypeOf: emtyInterface->reflect.Type
  • ValueOf: emtyInterface->reflect.Value

从反射对象中获得接口值

从 reflect.Value 数据结构可知,它包含了类型和值的信息,所以将 Value 转换成实例对象很容易。 将 Value 转换成空的 interface,内部存放具体类型实例。使用 interface() 函数。

func (v Value) Interface() (i interface{}) {
	return valueInterface(v, true)
}
v := reflect.ValueOf(1)
v.Interface().(int)

更新变量

当我们想要更新 reflect.Value 时,就需要调用 reflect.Value.Set 更新反射对象,该方法会调用 reflect.flag.mustBeAssignable 和 reflect.flag.mustBeExported 分别检查当前反射对象是否是可以被设置的以及字段是否是对外公开的(小写的变量不能修改):

调用 reflect.ValueOf 获取变量指针; 调用 reflect.Value.Elem 获取指针指向的变量; 调用 reflect.Value.SetInt 更新变量的值:

func main() {
	i := 1
	v := reflect.ValueOf(&i)
	v.Elem().SetInt(10)
	fmt.Println(i)
}

$ go run reflect.go
10

修改变量的方式

func main(){
i:=0
i=4 //直接操作i,i地址不变

v := &i // 获取i地址
*v = 10 // 通过地址获取指针指向的变量
}

反射类型

func main() {
        l:=[]string{}
        var str = "Hello World"
        
        typel := reflect.TypeOf(l)
        typeEle:=reflect.TypeOf(l).Elem()
        typeStr := reflect.TypeOf(str)
        
        typelKind := typel.Kind()
        typeEleKind:=typeEle.Kind()
        typeStrKind := typeStr.Kind()
        
        fmt.Println("l type =", typel, ", Kind =", typelKind)
        fmt.Println("l element type =", typeEle, ", Kind =", typeEleKind)
        fmt.Println("str type =", typeStr, ", Kind =", typeStrKind)


}

输出

l type = []string , Kind = slice
l element type = string , Kind = string
str type = string , Kind = string

typeof.Elem() 获取元素的类型,前提是接口类型必须是slice,map,ptr等,则panic.

应用场景

  • 获取结构体字段的指针。
  • 服务初始化时注册一些方法,对外暴露统一的接口,通过反射方式识别行参调用指定函数。

文档信息

Search

    Table of Contents