反射

寒江蓑笠翁大约 28 分钟

反射

反射是一种在运行时检查语言自身结构的机制,它可以很灵活的去应对一些问题,但同时带来的弊端也很明显,例如性能问题等等。在Go中,反射与interface{}密切相关,很大程度上,只要有interface{}出现的地方,就会有反射。Go中的反射API是由标准库reflect包提供的。


接口

在开始之前先简单的了解一下位于runtime包下的两个接口。在Go中,接口本质上是结构体,Go在运行时将接口分为了两大类,一类是没有方法集的接口,另一个类则是有方法集的接口。对于含有方法集的接口来说,在运行时由如下的结构体iface来进行表示

type iface struct {
   tab  *itab // 包含 数据类型,接口类型,方法集等
   data unsafe.Pointer // 指向值的指针
}

而对于没有方法集接口来说,在运行时由eface 结构体来进行表示,如下

type eface struct {
   _type *_type // 类型
   data  unsafe.Pointer // 指向值的指针
}

而这两个结构体在reflect包下都有与其对应的结构体类型,iface对应的是nonEmptyInterface

type nonEmptyInterface struct {
	itab *struct {
		ityp *rtype // 静态接口类型
		typ  *rtype // 动态具体类型
		hash uint32 // 类型哈希
		_    [4]byte
		fun  [100000]unsafe.Pointer // 方法集
	}
	word unsafe.Pointer // 指向值的指针
}

eface对应的是emptyInterface

type emptyInterface struct {
   typ  *rtype // 动态具体类型
   word unsafe.Pointer // 指向指针的值
}

对于这两种类型,官方给出了很明确的定义

  • nonEmptyInterface: nonEmptyInterface is the header for an interface value with methods
  • emptyInterface:emptyInterface is the header for an interface{} value

上述提到了动态具体类型这一词,原文为dynamic concrete type,首先Go语言是一个百分之百的静态类型语言,静态这一词是体现在对外表现的抽象的接口类型是不变的,而动态表示是接口底层存储的具体实现的类型是可以变化的。至此,对于接口的简单原理只需要了解到这里就足够满足后续反射的学习。

桥梁

reflect包下,分别有reflect.Type接口类型来表示Go中的类型,reflect.Value结构体类型来表示Go中的值

type Type interface {
    ...
    
    Name() string

	PkgPath() string

	Size() uintptr

	String() string

	Kind() Kind
    
    ...
}

type Value struct {
    
   typ *rtype

   ptr unsafe.Pointer

   flag
    
}

上面的代码省略了很多细节,先只需要了解这两个类型的存在即可。Go中所有反射相关的操作都是基于这两个类型,reflect包提供了两个函数来将Go中的类型转换为上述的两种类型以便进行反射操作,分别是reflect.TypeOf函数

func TypeOf(i any) Type 

reflect.ValueOf函数

func ValueOf(i any) Value 

可以看到两个函数的参数类型都是any,也就是interface{}的别名。如果想要进行反射操作,就需要先将其类型转换为interface{},这也是为什么前面提到了只要有反射就离不开空接口。不严谨的说,空接口就是连接Go类型系统与反射的桥梁,下图很形象的描述了其过程。

提示

下文中为了方便,统一使用别名any来替代interface{}


核心

在Go中有三个经典的反射定律,结合上面所讲的内容也就非常好懂,分别如下

  1. 反射可以将interface{}类型变量转换成反射对象

  2. 反射可以将反射对象还原成interface{}类型变量

  3. 要修改反射对象,其值必须是可设置的

这三个定律便是Go反射的核心,当需要访问类型相关信息时,便需要用到reflect.TypeOf,当需要修改反射值时,就需要用到reflect.ValueOf

类型

reflect.Type代表着Go中的类型,使用reflect.TypeOf()函数可以将变量转换成reflect.Type。代码示例如下

func main() {
	str := "hello world!"
	reflectType := reflect.TypeOf(str)
	fmt.Println(reflectType)
}

输出结果为

string

Kind

对于Type而言,Go内部使用reflect.Kind来表示Go中的基础类型,其本质上是无符号整型uint

type Kind uint

reflect包使用Kind枚举出了Go中所有的基础类型,如下所示

const (
   Invalid Kind = iota
   Bool
   Int
   Int8
   Int16
   Int32
   Int64
   Uint
   Uint8
   Uint16
   Uint32
   Uint64
   Uintptr
   Float32
   Float64
   Complex64
   Complex128
   Array
   Chan
   Func
   Interface
   Map
   Pointer
   Slice
   String
   Struct
   UnsafePointer
)

Kind类型仅仅实现了Stringer接口的String()方法,该类型也仅有这一个方法,String()方法的返回值来自于一个其内部的map,如下所示

var kindNames = []string{
   Invalid:       "invalid",
   Bool:          "bool",
   Int:           "int",
   Int8:          "int8",
   Int16:         "int16",
   Int32:         "int32",
   Int64:         "int64",
   Uint:          "uint",
   Uint8:         "uint8",
   Uint16:        "uint16",
   Uint32:        "uint32",
   Uint64:        "uint64",
   Uintptr:       "uintptr",
   Float32:       "float32",
   Float64:       "float64",
   Complex64:     "complex64",
   Complex128:    "complex128",
   Array:         "array",
   Chan:          "chan",
   Func:          "func",
   Interface:     "interface",
   Map:           "map",
   Pointer:       "ptr",
   Slice:         "slice",
   String:        "string",
   Struct:        "struct",
   UnsafePointer: "unsafe.Pointer",
}

type Type interface{
    Kind() Kind
}

通过Kind,可以知晓空接口存储的值究竟是什么基础类型,例如

func main() {
    // 声明一个any类型的变量
	var eface any
    // 赋值
	eface = 100
    // 通过Kind方法,来获取其类型
	fmt.Println(reflect.TypeOf(eface).Kind())
}

输出结果

int

Elem

type Type interface{
    Elem() Type
}

使用Type.Elem()方法,可以判断类型为any的数据结构所存储的元素类型,可接收的底层参数类型必须是指针,切片,数组,通道,映射表其中之一,否则会panic。下面是代码示例

func main() {
	var eface any
	eface = map[string]int{}
	rType := reflect.TypeOf(eface)
    // key()会返回map的键反射类型
	fmt.Println(rType.Key().Kind())
	fmt.Println(rType.Elem().Kind())
}

输出为

string
int

指针也可以理解为是一个容器,对于指针使用Elem()会获得其指向元素的反射类型,代码示例如下

func main() {
	var eface any
    // 赋值指针
	eface = new(strings.Builder)
	rType := reflect.TypeOf(eface)
    // 拿到指针所指向元素的反射类型
	vType := rType.Elem()
    // 输出包路径
	fmt.Println(vType.PkgPath())
    // 输出其名称
	fmt.Println(vType.Name())
}
strings
Builder

对于数组,切片,通道用使用起来都是类似的。

Size

type Type interface{
    Size() uintptr
}

通过Size方法可以获取对应类型所占的字节大小,示例如下

func main() {
	fmt.Println(reflect.TypeOf(0).Size())
	fmt.Println(reflect.TypeOf("").Size())
	fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
	fmt.Println(reflect.TypeOf(0.1).Size())
	fmt.Println(reflect.TypeOf([]string{}).Size())
}

输出结果为

8
16
16
8 
24

提示

使用unsafe.Sizeof()可以达到同样的效果

Comparable

type Type interface{
    Comparable() bool
}

通过Comparable方法可以判断一个类型是否可以被比较,例子如下

func main() {
	fmt.Println(reflect.TypeOf("hello world!").Comparable())
	fmt.Println(reflect.TypeOf(1024).Comparable())
	fmt.Println(reflect.TypeOf([]int{}).Comparable())
	fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}

输出如下

true
true 
false
true 

Implements

type Type interface{
    Implements(u Type) bool
}

通过Implements方法可以判断一个类型是否实现了某一接口

type MyInterface interface {
	My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
	return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
	return "his"
}

func main() {
	rIface := reflect.TypeOf(new(MyInterface)).Elem()
	fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
	fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}

输出结果

true
false

ConvertibleTo

type Type interface{
    ConvertibleTo(u Type) bool
}

使用ConvertibleTo方法可以判断一个类型是否可以被转换为另一个指定的类型

type MyInterface interface {
	My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
	return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
	return "his"
}

func main() {
	rIface := reflect.TypeOf(new(MyInterface)).Elem()
	fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
	fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}

输出

true
false

reflect.Value代表着反射接口的值,使用reflect.ValueOf()函数可以将变量转换成reflect.Value。代码示例如下

func main() {
	str := "hello world!"
	reflectValue := reflect.ValueOf(str)
	fmt.Println(reflectValue)
}

输出结果为

hello world!

Type

func (v Value) Type() Type

Type方法可以获取一个反射值的类型

func main() {
   num := 114514
   rValue := reflect.ValueOf(num)
   fmt.Println(rValue.Type())
}

输出

int

Elem

func (v Value) Elem() Value 

获取一个反射值的元素反射值

func main() {
   num := new(int)
   *num = 114514
   // 以指针为例子
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

输出

114514

指针

获取一个反射值的指针方式有两种

// 返回一个表示v地址的指针反射值
func (v Value) Addr() Value

// 返回一个指向v的原始值的uinptr 等价于 uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr

// 返回一个指向v的原始值的uintptr 
// 仅当v的Kind为 Chan, Func, Map, Pointer, Slice, UnsafePointer时,否则会panic
func (v Value) Pointer() uintptr

// 返回一个指向v的原始值的unsafe.Pointer
// 仅当v的Kind为 Chan, Func, Map, Pointer, Slice, UnsafePointer时,否则会panic
func (v Value) UnsafePointer() unsafe.Pointer 

示例如下

func main() {
   num := 1024
   ele := reflect.ValueOf(&num).Elem()
   fmt.Println("&num", &num)
   fmt.Println("Addr", ele.Addr())
   fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
   fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
   fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}

输出

&num 0xc0000a6058
Addr 0xc0000a6058         
UnsafeAddr 0xc0000a6058   
Pointer 0xc0000a6058      
UnsafePointer 0xc0000a6058

提示

fmt.Println会反射获取参数的类型,如果是reflect.Value类型的话,会自动调用Value.Interface()来获取其原始值。

换成一个map再来一遍

func main() {
	dic := map[string]int{}
	ele := reflect.ValueOf(&dic).Elem()
	println(dic)
	fmt.Println("Addr", ele.Addr())
	fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
	fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
	fmt.Println("UnsafePointer", ele.UnsafePointer())
}

输出

0xc00010e4b0
Addr &map[]               
UnsafeAddr 0xc00010e4b0   
Pointer 0xc00010e4b0      
UnsafePointer 0xc00010e4b0

设置值

func (v Value) Set(x Value)

倘若通过反射来修改反射值,那么其值必须是可取址的,这时应该通过指针来修改其元素值,而不是直接尝试修改元素的值。

func main() {
   // *int
   num := new(int)
   *num = 114514
   rValue := reflect.ValueOf(num)
    // 获取指针指向的元素
   ele := rValue.Elem()
   fmt.Println(ele.Interface())
   ele.SetInt(11)
   fmt.Println(ele.Interface())
}

输出如下

114514
11

获取值

func (v Value) Interface() (i any)

通过Interface()方法可以获取反射值原有的值

func main() {
   var str string
   str = "hello"
   rValue := reflect.ValueOf(str)
   if v, ok := rValue.Interface().(string); ok {
      fmt.Println(v)
   }
}

输出

hello

函数

通过反射可以获取函数的一切信息,也可以反射调用函数

信息

通过反射类型来获取函数的一切信息

func Max(a, b int) int {
   if a > b {
      return a
   }
   return b
}

func main() {
   rType := reflect.TypeOf(Max)
   // 输出函数名称,字面量函数的类型没有名称
   fmt.Println(rType.Name())
   // 输出参数,返回值的数量
   fmt.Println(rType.NumIn(), rType.NumOut())
   rParamType := rType.In(0)
   // 输出第一个参数的类型
   fmt.Println(rParamType.Kind())
   rResType := rType.Out(0)
   // 输出第一个返回值的类型
   fmt.Println(rResType.Kind())
}

输出


2 1
int
int

调用

通过反射值来调用函数

func (v Value) Call(in []Value) []Value 
func main() {
   // 获取函数的反射值
   rType := reflect.ValueOf(Max)
   // 传入参数数组
   rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
   for _, value := range rResValue {
      fmt.Println(value.Interface())
   }
}

输出

50

结构体

假设有如下结构体

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"age"`
	Address string `json:"address"`
	money   int
}

func (p Person) Talk(msg string) string {
	return msg
}

访问字段

reflect.StructField结构的结构如下

type StructField struct {
	// 字段名称
	Name string
	// 包名
	PkgPath string
	// 类型名
	Type      Type      
	// Tag
	Tag       StructTag 
	// 字段的字节偏移
	Offset    uintptr   
	// 索引
	Index     []int    
	// 是否为嵌套字段
	Anonymous bool      
}

访问结构体字段的方法有两种,一种是通过索引来进行访问,另一种是通过名称。

type Type interface{
    Field(i int) StructField
}

通过索引访问的例子如下

func main() {
	rType := reflect.TypeOf(new(Person)).Elem()
	// 输出结构体字段的数量
	fmt.Println(rType.NumField())
	for i := 0; i < rType.NumField(); i++ {
		structField := rType.Field(i)
		fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
	}
}

输出

4
[0] Name string 0 true    
[1] Age int 16 true       
[2] Address string 24 true
[3] money int 40 false  

type Type interface{
    FieldByName(name string) (StructField, bool)
}

通过名称访问的例子如下

func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   // 输出结构体字段的数量
   fmt.Println(rType.NumField())
   if field, ok := rType.FieldByName("money"); ok {
      fmt.Println(field.Name, field.Type, field.IsExported())
   }
}

输出

4
money int false

修改字段

倘若要修改结构体字段值,则必须传入一个结构体指针,下面是一个修改字段的例子

func main() {
	// 传入指针
	rValue := reflect.ValueOf(&Person{
		Name:    "",
		Age:     0,
		Address: "",
		money:   0,
	}).Elem()

	// 获取字段
	name := rValue.FieldByName("Name")
	// 修改字段值
	if (name != reflect.Value{}) { // 如果返回reflect.Value{},则说明该字段不存在
		name.SetString("jack")
	}
	// 输出结构体
	fmt.Println(rValue.Interface())
}

输出

{jack 0  0}

对于修改结构体私有字段而言,需要进行一些额外的操作,如下

func main() {
	// 传入指针
	rValue := reflect.ValueOf(&Person{
		Name:    "",
		Age:     0,
		Address: "",
		money:   0,
	}).Elem()

	// 获取一个私有字段
	money := rValue.FieldByName("money")
	// 修改字段值
	if (money != reflect.Value{}) {
		// 构造指向该结构体未导出字段的指针反射值
		p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
		// 获取该指针所指向的元素,也就是要修改的字段
		field := p.Elem()
		// 修改值
		field.SetInt(164)
	}
	// 输出结构体
	fmt.Printf("%+v\n", rValue.Interface())
}

访问Tag

获取到StructField后,便可以直接访问其Tag

// 如果不存在,ok为false
func (tag StructTag) Lookup(key string) (value string, ok bool)

// 如果不存在,返回空字符串
func (tag StructTag) Get(key string) string

示例如下

func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   name, ok := rType.FieldByName("Name")
   if ok {
      fmt.Println(name.Tag.Lookup("json"))
      fmt.Println(name.Tag.Get("json"))
   }
}

输出

name true
name

访问方法

访问方法与访问字段的过程很相似,只是函数签名略有区别。reflect.Method结构体如下

type Method struct {
	// 方法名
	Name string
	// 包名
	PkgPath string
	// 方法类型
	Type  Type 
	// 方法对应的函数,第一个参数是接收者
	Func  Value 
	// 索引
	Index int
}

访问方法信息示例如下

func main() {
	// 获取结构体反射类型
	rType := reflect.TypeOf(new(Person)).Elem()
	// 输出方法个数
	fmt.Println(rType.NumMethod())
	// 遍历输出方法信息
	for i := 0; i < rType.NumMethod(); i++ {
		method := rType.Method(i)
		fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
	}
}

输出

1
0 Talk func(main.Person, string) string true

如果想要获取方法的参数和返回值细节,可以通过Method.Func来进行获取,过程与访问函数信息一致,将上面的代码稍微修改下

func main() {
	// 获取结构体反射类型
	rType := reflect.TypeOf(new(Person)).Elem()
	// 输出方法个数
	fmt.Println(rType.NumMethod())
	// 遍历输出方法信息
	for i := 0; i < rType.NumMethod(); i++ {
		method := rType.Method(i)
		fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
		fmt.Println("方法参数")
		for i := 0; i < method.Func.Type().NumIn(); i++ {
			fmt.Println(method.Func.Type().In(i).String())
		}
		fmt.Println("方法返回值")
		for i := 0; i < method.Func.Type().NumOut(); i++ {
			fmt.Println(method.Func.Type().Out(i).String())
		}
	}
}

可以看到第一个参数是main.Person,也就是接收者类型

1
0 Talk func(main.Person, string) string true
方法参数                                    
main.Person                                 
string                                      
方法返回值                                  
string                                      

调用方法

调用方法与调用函数的过程相似,而且并不需要手动传入接收者,例子如下

func main() {
   // 获取结构体反射类型
   rValue := reflect.ValueOf(new(Person)).Elem()
   // 输出方法个数
   fmt.Println(rValue.NumMethod())
   // 遍历输出方法信息
   talk := rValue.MethodByName("Talk")
   if (talk != reflect.Value{}) {
      // 调用方法,并获取返回值
      res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
      // 遍历输出返回值
      for _, re := range res {
         fmt.Println(re.Interface())
      }
   }
}

输出

1
hello,reflect!

创建

通过反射可以构造新的值,reflect包同时根据一些特殊的类型提供了不同的更为方便的函数。

基本类型

// 返回指向反射值的指针反射值
func New(typ Type) Value

string为例

func main() {
   rValue := reflect.New(reflect.TypeOf(*new(string)))
   rValue.Elem().SetString("hello world!")
   fmt.Println(rValue.Elem().Interface())
}
hello world!

结构体

结构体的创建同样用到reflect.New函数

type Person struct {
   Name    string `json:"name"`
   Age     int    `json:"age"`
   Address string `json:"address"`
   money   int
}

func (p Person) Talk(msg string) string {
   return msg
}

func main() {
   // 创建结构体反射值
   rType := reflect.TypeOf(new(Person)).Elem()
   person := reflect.New(rType).Elem()
   fmt.Println(person.Interface())
}

输出

{ 0  0}

切片

反射创建切片

func MakeSlice(typ Type, len, cap int) Value
func main() {
   // 创建切片反射值
   rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
   // 遍历赋值
   for i := 0; i < 10; i++ {
      rValue.Index(i).SetInt(int64(i))
   }
   fmt.Println(rValue.Interface())
}
[0 1 2 3 4 5 6 7 8 9]

Map

反射创建Map

func MakeMapWithSize(typ Type, n int) Value 
func main() {
   //构建map反射值
   rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
   // 设置值
   rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
   fmt.Println(rValue.Interface())
}
map[a:1]

管道

反射创建管道

func MakeChan(typ Type, buffer int) Value 
func main() {
   // 创建管道反射值
   makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
   fmt.Println(makeChan.Interface())
}

函数

反射创建函数

func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
func main() {
    // 传入包装类型和函数体
	fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
		for _, arg := range args {
			fmt.Println(arg.Interface())
		}
		return nil
	})
	fmt.Println(fn.Type())
	fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}

输出

func(int)
1024

深度相等

reflect.DeepEqual是反射包下提供的一个用于判断两个变量是否深度相等的函数,签名如下。

func DeepEqual(x, y any) bool

该函数对于每一种基础类型都做了处理,下面是一些类型判断方式。

  • 数组:数组中的每一个元素都深度相等
  • 切片:都为nil时,判为深度相等,或者都不为空时,长度范围内的元素深度相等
  • 结构体:所有字段都深度相等
  • 映射表:都为nil时,为深度相等,都不为nil时,每一个键所映射的值都深度相等
  • 指针:指向同一个元素或指向的元素深度相等
  • 接口:接口的具体类型深度相等时
  • 函数:只有两者都为nil时才是深度相等,否则就不是深度相等

下面是一些例子:

切片

func main() {
   a := make([]int, 100)
   b := make([]int, 100)
   fmt.Println(reflect.DeepEqual(a, b))
}

输出

true

结构体

func main() {
   mike := Person{
      Name:   "mike",
      Age:    39,
      Father: nil,
   }

   jack := Person{
      Name:   "jack",
      Age:    18,
      Father: &mike,
   }

   tom := Person{
      Name:   "tom",
      Age:    18,
      Father: &mike,
   }
   fmt.Println(reflect.DeepEqual(mike, jack))
   fmt.Println(reflect.DeepEqual(tom, jack))
   fmt.Println(reflect.DeepEqual(jack, jack))
}

输出

false
false
true