go中的反射详解
反射基础
Go语言的类型:
-
变量包括(type, value)两部分
- 解这一点就知道为什么nil != nil了
-
type 包括 static type和concrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型
-
类型断言能否成功,取决于变量的concrete type,而不是static type。因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer。
Go语言的反射就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。
在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:
1(value, type)
1// ValueOf returns a new Value initialized to the concrete value
2// stored in the interface i. ValueOf(nil) returns the zero
3func ValueOf(i interface{}) Value {...}
4
5//翻译一下:ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
6
7
8// TypeOf returns the reflection Type that represents the dynamic type of i.
9// If i is a nil interface value, TypeOf returns nil.
10func TypeOf(i interface{}) Type {...}
11
12//翻译一下:TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
reflect.TypeOf()是获取pair中的type,reflect.ValueOf()获取pair中的value
首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数。
1t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
2v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
反射它给我们提供了在运行时对代码本身进行访问和修改对能力。通过反射,我们可以拿到很多信息,如变量的字段名称、类型信息和结构体信息等,并使用这些类型信息做一些灵活的操作。
go语言的反射主要通过Type和Value 两个基本概念来表达。
Type 主要用来表达反射变量的类型信息 (无法取到变量的值和 无法修改值)
Value 用于表示被反射变量的实例信息。
reflect.Type 类型对象
go 语言中 存在着Type(类型) 和 kind(种类)的区别,下面案例 Hero的类型是main.Hero ,而种类是 struct
- reflect.Typeof() 获得类型对象
- reflect.Typeof().Kind() 获得种类
- reflect.Typeof().Elem() 获得 指针指向的实体。 It panics if the type’s Kind is not Array, Chan, Map, Pointer, or Slice.
代码示例:
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type Hero struct {
9 Name string
10 Age int
11 Speed int
12}
13
14func (hero *Hero) SayHello(name string) {
15 fmt.Println("Hello "+name, ", I am "+hero.Name)
16}
17
18func main() {
19 typeOfHero := reflect.TypeOf(Hero{})
20 fmt.Printf("Heor's type is %s, kind is %s \n", typeOfHero, typeOfHero.Kind()) // Heor's type is main.Hero, kind is struct
21
22 typeOfPtrHero := reflect.TypeOf(&Hero{})
23 fmt.Printf("*Heor's type is %s, kind is %s \n", typeOfPtrHero, typeOfPtrHero.Kind()) // *Heor's type is *main.Hero, kind is ptr
24 fmt.Println(typeOfPtrHero.Elem()) // main.Hero // Elem() 获得*main.Hero 指针指向的实体
25}
种类有, 见 reflect/type.go
1type Kind uint
2
3const (
4 Invalid Kind = iota
5 Bool
6 Int
7 Int8
8 Int16
9 Int32
10 Int64
11 Uint
12 Uint8
13 Uint16
14 Uint32
15 Uint64
16 Uintptr
17 Float32
18 Float64
19 Complex64
20 Complex128
21 Array
22 Chan
23 Func
24 Interface
25 Map
26 Pointer
27 Slice
28 String
29 Struct
30 UnsafePointer
31)
类型对象reflect.StructField 和 refect.Method
Type接口提供了用于获得 字段结构体域类型对象(StructField)的方法主要有3个方法:
- NumField() int
- Field(i int) StructField
- FieldByName(name string) (StructField,bool)
1type StructField struct {
2 //成员字段的名称
3 Name string
4 // 成员字段的类型 Type
5 Type Type
6 // Tag
7 Tag StructTag
8 //字段偏移
9 Offset uintptr
10 // 成员字段的index
11 Index []int
12 //成员字段是否公开
13 Anonymous bool
14
15}
Type还提供了获取接口下方法的方法类型对象 Method
- Method(ind) Method //根据index查找方法
- MethodByName(string) (Method,bool) //根据方法名查找方法
- NumMethod() int //获取类型中公开的方法数量
1type Method struct {
2 // 方法名
3 Name string
4 //方法类型
5 Type Type
6 // 反射对象,可用于调用方法
7 Func Value
8 // 方法的index
9 Index int
10}
在 Method 中 Func 字段是一个反射值对象,可以用于方法的调用,如果Method是来自于接口反射得到的Type,那么Func传递的第一个参数需要为实现方法的接收器
代码示例:
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8// 定义一个人的接口
9type Person interface {
10
11 // 和人说hello
12 SayHello(name string)
13 // 跑步
14 Run() string
15}
16
17type Hero struct {
18 Name string
19 Age int
20 Speed int
21}
22
23func (hero *Hero) SayHello(name string) {
24 fmt.Println("Hello "+name, ", I am "+hero.Name)
25}
26
27func (hero *Hero) Run() string {
28 fmt.Println("i am running at speed:", hero.Speed)
29 return "Running"
30}
31
32func main() {
33 typeOfHero := reflect.TypeOf(Hero{})
34 /*
35 field's name is Name,type is string ,kind is string
36 field's name is Age,type is int ,kind is int
37 field's name is Speed,type is int ,kind is int
38 */
39 // 打印 Hero结构体的字段名,类型,种类
40 // 通过 NumField()获得字段的数量
41 for i := 0; i < typeOfHero.NumField(); i++ {
42 fmt.Printf("field's name is %s,type is %s ,kind is %s\n", typeOfHero.Field(i).Name, typeOfHero.Field(i).Type, typeOfHero.Field(i).Type.Kind())
43 }
44 //获得名称为Name 的成员字段类型对象
45 nameField, _ := typeOfHero.FieldByName("Name")
46 // filed's name is Name,type is string, kind is string
47 fmt.Printf("filed's name is %s,type is %s, kind is %s\n", nameField.Name, nameField.Type, nameField.Type.Kind())
48
49 var person Person = &Hero{}
50 // 获取接口Person的类型对象
51 typeOfPerson := reflect.TypeOf(person)
52 // 打印Person的方法类型和名称
53 // method is Run, type is func(*main.Hero) string, kind is func.
54 // method is SayHello, type is func(*main.Hero, string), kind is func.
55 for i := 0; i < typeOfPerson.NumMethod(); i++ {
56 fmt.Printf("method is %s, type is %s, kind is %s.\n",
57 typeOfPerson.Method(i).Name,
58 typeOfPerson.Method(i).Type,
59 typeOfPerson.Method(i).Type.Kind())
60 }
61 method, _ := typeOfPerson.MethodByName("Run")
62 // method is Run, type is func(*main.Hero) string, kind is func.
63 fmt.Printf("method is %s, type is %s, kind is %s.\n", method.Name, method.Type, method.Type.Kind())
64}
reflect.Value 反射值对象
使用Type类型对象可以获取到变量的类型与种类,但是无法获取到变量的值,更无法对值进行修改。
使用reflect.ValueOf取 反射变量的实例信息 Value,通过Value 对变量的值进行查看和修改。
获取变量的值
Value 提供用于获取变量的值的方法
- func(v Value) Interface() (i interface{})
- func(v Value) Int() int64
- func(v Value) Float() float64
- func(v Value) Bytes() []byte()
- func(v Value) String() string
- func(v Value) Bool() bool
如果取得变量的类型与取值的方法不匹配会panic
reflect.New
reflect.New 方法根据变量Type对象创建一个相同类型的新变量,值以Value对象的形式返回
1typeOfHero := reflect.TypeOf(Hero{})
2// reflect.New 方法根据变量Type对象创建一个相同类型的新变量,值以Value对象的形式返回
3heroValue := reflect.New(typeOfHero)
4//输出: Hero's type is *main.Hero ,kind is ptr
5fmt.Printf("Hero's type is %s ,kind is %s\n", heroValue.Type(), heroValue.Kind())
对变量修改 Value.Set
对变量对修改可以通过Value.Set方法实现
1// 通过Value.Set方法修改变量的值
2userName := "小帅"
3valueOfUserName := reflect.ValueOf(&userName)
4valueOfUserName.Elem().Set(reflect.ValueOf("小米"))
5//输出: 小米
6fmt.Println(userName)
Value.CanAddr
一个变量的Value是否可寻址 可以通过Value.CanAddr方法来判断
1name := "小明"
2valueOfName := reflect.ValueOf(name)
3fmt.Printf("name can be address :%t\n", valueOfName.CanAddr()) // false
4valueOfName = reflect.ValueOf(&name)
5fmt.Printf("&name can be address :%t\n", valueOfName.CanAddr()) // false
6valueOfName = valueOfName.Elem()
7fmt.Printf("&name's Elem can be address :%t\n", valueOfName.CanAddr()) // true
只有指针类型的解引用后的Value才是可寻址的
Value.CanSet
针对结构体类型对变量雷说,结构体内对字段不仅要被能够寻址,还需要公开才能修改该值。
1hero := &Hero{
2 Name: "小白",
3}
4valueOfHero := reflect.ValueOf(hero).Elem()
5valueOfHerorName := valueOfHero.FieldByName("Name")
6if valueOfHerorName.CanSet() {
7 valueOfHerorName.Set(reflect.ValueOf("小章"))
8}
9fmt.Println(hero.Name) // 小章
反射调用函数
- func (v Value) Call(in []Value) []Value
- 底层会把方法的接收器Value对象传递过去,下面代码接收器就是person对象
代码示例:
1 //使用反射调用接口方法
2 var person Person = &Hero{
3 Name: "小红",
4 Speed: 100,
5 }
6 valueOfPerson := reflect.ValueOf(person)
7 //获取SayHello方法
8 sayHelloMethod := valueOfPerson.MethodByName("SayHello") // Hello 小张 , I am 小红
9 //构建调用参数 通过Value.Call调用方法
10 sayHelloMethod.Call([]reflect.Value{reflect.ValueOf("小张")}) // i am running at speed: 100
11 //获取Run方法
12 runMethod := valueOfPerson.MethodByName("Run")
13 result := runMethod.Call([]reflect.Value{})
14 fmt.Println("result of run method is ", result[0]) // result of run method is Running
通过Type对象,需要调用时,把方法的接收器放在 in []Value 参数列表的第一位
1
2 var person Person = &Hero{
3 Name: "小红",
4 Speed: 100,
5 }
6 typeOfPerson := reflect.TypeOf(person)
7 rMethod, _ := typeOfPerson.MethodByName("Run")
8 //将person接收器放在参数第一位,否则会抛异常 too few input arguments
9 result = rMethod.Func.Call([]reflect.Value{reflect.ValueOf(person)})
10 fmt.Println(result[0]) //Running
反射调用普通函数
1func hello(){
2 fmt.Println("hello world")
3}
4methodOfHello :=reflect.ValueOf(hello)
5methodOfHello.Call([]reflect.Value{}) //输出: hello world
遍历结构体字段
1
2type User struct {
3 Name string `json:"name"`
4 Age int64 `json:"age"`
5}
6
7func TestIterateField(t *testing.T) {
8 u := &User{
9 Name: "crmao",
10 Age: 32,
11 }
12 u2 := &u
13
14 tests := []struct {
15 name string
16 val any
17 wantRes map[string]any
18 wantErr error
19 }{{
20 name: "nil",
21 val: nil,
22 wantErr: errors.New("不能 nil"),
23 },
24 {
25 name: "struct",
26 val: User{
27 Name: "crmao",
28 Age: 32,
29 },
30 wantErr: nil,
31 },
32 {
33 name: "struct",
34 val: u2,
35 wantErr: nil,
36 },
37 }
38
39 for _, cas := range tests {
40 t.Run(cas.name, func(t *testing.T) {
41 res, err := iterateField(cas.val)
42 assert.Equal(t, cas.wantErr, err)
43 if err != nil {
44 return
45 }
46 fmt.Println(res)
47 })
48
49 }
50}
51
52func iterateField(val any) (map[string]any, error) {
53 if val == nil {
54 return nil, errors.New("不能 nil")
55 }
56 typ := reflect.TypeOf(val)
57 refVal := reflect.ValueOf(val)
58 // 可能是地址(一级指针),也可能是多级指针
59 for typ.Kind() == reflect.Ptr {
60 typ = typ.Elem()
61 refVal = refVal.Elem()
62 }
63 numField := typ.NumField()
64 res := make(map[string]any, numField)
65 for i := 0; i < numField; i++ {
66 fdType := typ.Field(i)
67 res[fdType.Name] = refVal.Field(i).Interface()
68 }
69 return res, nil
70}