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}