1.介绍

Go语言实现了反射,所谓反射就是动态运行时的状态。我们一般用到的包是reflect包,reflect 实现了运行时的反射能力,能够让程序操作不同类型的对象。反射包中有两对非常重要的函数和类型,两个函数分别是:

方法名 描述
reflect.TypeOf 可以获得任意值的类型对象,返回类型:reflect.Type
reflect.ValueOf 可以获得任意值的值对象,返回类型:reflect.Value

反射包中的所有方法基本都是围绕着 reflect.Typereflect.Value 两个类型设计的。我们通过 reflect.TypeOfreflect.ValueOf 可以将一个普通的变量转换成反射包中提供的 reflect.Typereflect.Value,随后就可以使用反射包中的方法对它们进行复杂的操作。

2.reflect.Type

类型 reflect.Type 是反射包定义的一个接口,我们可以使用 reflect.TypeOf 函数获取任意变量的类型,reflect.Type 接口中定义了一些方法,常用的如下:

2.1 常用方法列表

方法名 描述
Kind() Kind 返回该变量的的具体分类
Name() string Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
NumField() int 返回 struct 字段数量,不是 structpanic
Implements(u Type) bool 检查当前类型有没有实现接口 u
Field(i int) StructField 返回struct 类型的第 i 个字段,不是structpanic
i 越界也会panic
Key() Type 返回mapkey 的类型,不是mappanic

2.2 使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main
import (
	"fmt"
	"reflect"
)
type People interface {
	Eat(str string)
}
type User struct {
	Name string
	Age  int
}
func (u User) Eat(str string) {
	fmt.Println("吃" + str)
}
func main() {
	var user User
	userTypeOf := reflect.TypeOf(user)
	fmt.Printf("typeOf:%T v:%v\n", userTypeOf, userTypeOf)
	// 获取类型分类
	fmt.Printf("kind:%T v:%v\n", userTypeOf.Kind(), userTypeOf.Kind())
	// 获取类型名称
	fmt.Printf("name:%T v:%v\n", userTypeOf.Name(), userTypeOf.Name())
	// 判断类型是否实现接口People
	implements := userTypeOf.Implements(reflect.TypeOf((*People)(nil)).Elem())
	fmt.Printf("变量user是否实现接口People : %t\n", implements)
	// 变量结构体user的字段数量
	fmt.Printf("变量结构体user的字段数量: %d \n", userTypeOf.NumField())
	// 打印结构体字段
	fmt.Printf("打印结构体user第%d个字段: %v \n", 0,userTypeOf.Field(0).Name)
	fmt.Printf("打印结构体user第%d个字段: %v \n", 1,userTypeOf.Field(1).Name)
	map1 := map[string]int {
		"张三": 19,
		"李四": 23,
	}
	fmt.Printf("打印map的key类型: %v \n", reflect.TypeOf(map1).Key())
}

/**输出
typeOf:*reflect.rtype v:main.User
kind:reflect.Kind v:struct
name:string v:User
变量user是否实现接口People : true
变量结构体user的字段数量: 2 
打印结构体user第0个字段: Name 
打印结构体user第1个字段: Age 
打印map的key类型: string 
*/

2.3 Kind类型整理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

3.reflect.Value

反射包中Value的类型与Type不同,它被声明成了结构体。这个结构体没有对外暴露的字段,但是提供了获取或者写入数据的方法。

3.1 常用方法列表

方法名 描述
Call(in []Value) []Value 调用方法,第一个参数是in[0],第二个是in[1],以此类推
Field(i int) StructField 返回struct 类型的第 i 个字段的值,不是structpanic
i 越界也会panic
FieldByName(name string) Value 根据字段名查找值,前提类型是struct
Index(i int) Value 返回第 i 个元素,主要用于遍历,不能越界。
前提类型是Array, Slice, String之一,
IsNil() bool 返回值是否为nil。如果值类型不是通道(channel)、函数、
接口、map、指针或 切片时发生panic,类似于语言层的v== nil操作。常被用于判断指针是否为空。
IsValid() bool 返回v是否持有一个值。如果vValue零值会返回假,
此时v除了IsValid、String、Kind之外的方法都会导致panic常被用于判定返回值是否有效
MapIndex(key Value) Value 根据索引获取对应的值,前提类型是map
MapKeys() []Value 返回map中所有的key,返回类型是切片slice
Set(x Value) 通过反射修改值。
Type() Type 获取值的类型

3.2 使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main
import (
	"fmt"
	"reflect"
)
type User struct {
	Name string
	Age  int
}
func (u User) Eat(str string) {
	fmt.Println("调用函数: 吃" + str)
}
func main() {
	user := User{"张福生", 30}
	userValOf := reflect.ValueOf(user)
	// 调用方法
	param := []reflect.Value{reflect.ValueOf("香蕉")}
	userValOf.MethodByName("Eat").Call(param)
	// 打印结构体的值
	fmt.Printf("打印结构体user第%d个字段值v: %v\n", 0, userValOf.Field(0))
	fmt.Printf("打印结构体user第%d个字段值v: %v\n",1, userValOf.Field(1))
	// 根据属性值打印结构体的值
	fmt.Printf("打印结构体user字段%s的值v: %v\n", "Age",userValOf.FieldByName("Age"))
	// 返回第 i 个元素,主要用于遍历,不能越界。前提类型是Array, Slice, String之一,
	strSlice :=[]string{"你","我","他","!"}
	strSliceValOf := reflect.ValueOf(strSlice)
	fmt.Printf("打印字符串第%d个元素值v: %v\n", 0,strSliceValOf.Index(0))
	fmt.Printf("打印字符串第%d个元素值v: %v\n", 1,strSliceValOf.Index(1))
	fmt.Printf("打印字符串第%d个元素值v: %v\n", 3,strSliceValOf.Index(2))

	// IsNil: 判断是否是nil
	fmt.Printf("变量strSlice的值是否是nil: %t\n",strSliceValOf.IsNil())
	var u []User
	uuOf := reflect.ValueOf(u)
	fmt.Printf("IsNil: 变量u的值是否是nil: %t\n",uuOf.IsNil())
	// IsValid:判断返回值是否有效
	fmt.Printf("存在的方法: %t\n",userValOf.MethodByName("Eat").IsValid())
	fmt.Printf("不存在的方法: %t\n",userValOf.MethodByName("A").IsValid())

	// #####  针对map的操作  #####
	map1 := map[string]string{
		"姓名": "张三",
		"phone": "176666666666",
		"?": "ABC",
	}
	mapValOf := reflect.ValueOf(map1)
	// 根据索引获取对应的值
	fmt.Printf("根据索引获取map值,key=%s,v=%s \n","姓名",mapValOf.MapIndex(reflect.ValueOf("姓名")))
	fmt.Printf("根据索引获取map值,key=%s,v=%s \n","phone",mapValOf.MapIndex(reflect.ValueOf("phone")))
	fmt.Printf("根据索引获取map值,key=%s,v=%s \n","?",mapValOf.MapIndex(reflect.ValueOf("?")))
	// 返回map中所有的key,返回类型是切片slice
	fmt.Printf("返回map中所有的key: %+v 类型:%T \n",mapValOf.MapKeys(),mapValOf.MapKeys())
	// 通过反射修改值
	num := 10
	numValOf := reflect.ValueOf(&num)//注意这里需要传地址
	fmt.Printf("修改前的值: %v \n",num)
	numValOf.Elem().Set(reflect.ValueOf(100))
	fmt.Printf("修改后的值: %v \n",num)
	// 获取值的类型
	fmt.Printf("获取mapValOf值的类型: %v \n",mapValOf.Type())
}

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
调用函数: 吃香蕉
打印结构体user第0个字段值v: 张福生
打印结构体user第1个字段值v: 30
打印结构体user字段Age的值v: 30
打印字符串第0个元素值v: 你
打印字符串第1个元素值v: 我
打印字符串第3个元素值v: 他
变量strSlice的值是否是nil: false
IsNil: 变量u的值是否是nil: true
存在的方法: true
不存在的方法: false
根据索引获取map值,key=姓名,v=张三 
根据索引获取map值,key=phone,v=176666666666 
根据索引获取map值,key=?,v=ABC 
返回map中所有的key: [姓名 phone ?] 类型:[]reflect.Value 
修改前的值: 10 
修改后的值: 100 
获取mapValOf值的类型: map[string]string 

4.实践场景

4.1 动态调用结构体方法

使用MethodByName("xx").Call(param)调用函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * @Author Shershon
 * @Description 反射学习使用
 * @Date 2021/2/2 10:50 上午
 **/
package main
import (
	"fmt"
	"reflect"
)
type People struct {
	Name string
}
func (p People) GetName() string {
	return p.Name
}
func (p People) Tell(toName, context string) string {
	return fmt.Sprintf(p.Name+"告诉%s,%s", toName, context)
}

func main() {
	p := People{"张三"}
	peopleValOf := reflect.ValueOf(p)
	// 通过反射调用方法(无参数)
	call := peopleValOf.MethodByName("GetName").Call(nil)
	fmt.Printf("无参函数调用,返回: %v %T \n", call, call)

	// 通过反射调用方法(有参数)
	param := []reflect.Value{
		reflect.ValueOf("小明"),
		reflect.ValueOf("你通过考试了!"),
	}
	values := peopleValOf.MethodByName("Tell").Call(param)
	fmt.Printf("有参函数调用,返回: %v %T \n", values, values)
}
/**输出
无参函数调用,返回: [张三] []reflect.Value 
有参函数调用,返回: [张三告诉小明,你通过考试了!] []reflect.Value 
*/

4.2 处理错误结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main
import (
	"errors"
	"fmt"
	"reflect"
)
type People struct {
	Name string
}
func (p People)ThrowError()error  {
	return errors.New("测试错误抛出")
}
func main() {
	p := People{"张三"}
	peopleValOf := reflect.ValueOf(p)
	ret := peopleValOf.MethodByName("ThrowError").Call(nil)
	// 返回值也是 Value 类型,对于错误,可以转为 interface 之后断言
	fmt.Printf("errValue: %v 类型转换: %T", ret[0], ret[0].Interface().(error))
}

4.3 解析结构体标签

使用field.Tag.Lookup()field.Tag.Get()查找对应的标签值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
import (
	"fmt"
	"reflect"
)
type People struct {
	Name string `json:"name" db:"t_name"`
	Age int `json:"age" db:"t_age"`
}
func main() {
	p := People{"张三",23}
	peopleTypeOf := reflect.TypeOf(p)
	// 获取结构体字段数量
	fieldNum := peopleTypeOf.NumField()
	for i := 0; i < fieldNum; i++ {
		// 获取结构体中,第i个字段的值
		field := peopleTypeOf.Field(i)
		// 获取json标签对应的值(方法一)
		if lookup, ok := field.Tag.Lookup("json");ok {
			fmt.Printf("字段 %v, 对应的json-tag: %s \n" ,field.Name, lookup)
		}
		// 获取db标签对应的值(方法二)
		dbTag := field.Tag.Get("db")
		fmt.Printf("字段 %v, 对应的db-tag: %s \n" ,field.Name, dbTag)
	}
}
/**输出
字段 Name, 对应的json-tag: name 
字段 Name, 对应的db-tag: t_name 
字段 Age, 对应的json-tag: age 
字段 Age, 对应的db-tag: t_age 
*/

4.4 判断是否实现接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import (
	"fmt"
	"reflect"
)
type People interface {
	Eat(str string)
}
type User struct {}
func (u User) Eat(str string) {
	fmt.Println("吃" + str)
}
func main() {
	user := &User{}
	// 当变量不是传指针时,Eat方法的接收方是指针,则没有实现接口
	userTypeOf := reflect.TypeOf(user)
	// 判断类型是否实现接口People
	implements := userTypeOf.Implements(reflect.TypeOf((*People)(nil)).Elem())
	fmt.Printf("变量user是否实现接口People : %t\n", implements)
}
// 输出: 变量user是否实现接口People : false