众所周知,反射是框架设计的灵魂。反射在很多语言中都有其妙用。在计算机科学领域,反射是指一类应用,它们能够自描述自控制。本文将对于Golang的反射的笔记。

反射的用途

Golang提供了一种机制,在编译时不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射。

为什么用反射

目的就是增加程序的灵活性,避免将程序写死在代码里。借助反射透视一个未知的类型。

为何需要反射?

使用反射

reflect提供了两种类型来进行访问接口变量的内容

First Header Second Header
reflect.ValueOf() 获取输入参数接口中的数据的值,如果为空则返回0 <- 注意是0
reflect.TypeOf() 动态获取输入参数接口中的值的类型,如果为空则返回nil <- 注意是nil

简单的反射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func main() {
	var name string = "我是反射"
	//TypeOf会返回目标数据的类型,比如int/float/struct/指针等
	reflectType := reflect.TypeOf(name)

	//valueOf返回目标数据的的值,比如上文的"我是反射"
	reflectValue := reflect.ValueOf(name)

	fmt.Println("type: ", reflectType)
	fmt.Println("value: ", reflectValue)
}
//输出:
//type:  string
//value:  我是反射

结构体的反射

 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
type User struct {
	Id   int
	Name string
}

func (u User) Hello() {
	fmt.Println("hello")
}

func TestReflect(t interface{}) {
	objT := reflect.TypeOf(t)
	objV := reflect.ValueOf(t)

	//获取去这个类型的名称
	fmt.Println("这个类型的名称是:", objT.Name())
  
	//通过.NumField()来获取结构体里的字段数量
	for i := 0; i < objT.NumField(); i++ {
		//从0开始获取结构体所包含的key
		key := objT.Field(i)
		//从0开始通过interface方法来获取key所对应的值
		value := objV.Field(i).Interface()

		fmt.Printf("第%d个字段是:%s:%v = %v \n", i+1, key.Name, key.Type, value)
	}
	//通过.NumMethod()来获取结构体里的方法数量  这里只能获取 值接收器的方法。
	for i := 0; i < objT.NumMethod(); i++ {
		m := objT.Method(i)
		fmt.Printf("第%d个方法是:%s:%v\n", i+1, m.Name, m.Type)
	}

}
func main() {
	u := User{
		Id:   1,
		Name: "反射",
	}
	TestReflect(u)
}

输出:

这个类型的名称是: User
第1个字段是:Id:int = 1 
第2个字段是:Name:string = 反射 
第1个方法是:Hello:func(main.User)

匿名或嵌入字段的反射

 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
type User struct {
	Student //匿名字段
}

type Student struct {
	Id   int
	Name string
}

func main() {
	u := User{Student{
		Id:   1,
		Name: "反射",
	}}

	t := reflect.TypeOf(u)
	//这里需要加一个#号,可以把struct的详情都给打印出来
	//会发现有Anonymous:true,说明是匿名字段
	fmt.Printf("%#v\n", t.Field(0))

	fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1}))

	//获取匿名字段的值的详情
	v := reflect.ValueOf(u)
	fmt.Printf("%#v\n", v.Field(0))
}

输出:

reflect.StructField{Name:"Student", PkgPath:"", Type:(*reflect.rtype)(0x10bb640), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x10ae120), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
main.Student{Id:1, Name:"反射"}

用反射判断传入的类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Student struct {
	Id   int
	Name string
}

func main() {
	s := Student{Id: 1, Name: "反射"}
	t := reflect.TypeOf(s)

	//通过.Kind()来判断对比的值是否是struct类型
	if k := t.Kind(); k == reflect.Struct {
		fmt.Println("bingo")
	}

	num := 1;
	numType := reflect.TypeOf(num)
	if k := numType.Kind(); k == reflect.Int {
		fmt.Println("bingo")
	}
}
/*输出:
bingo
bingo
*/

通过反射修改内容

 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
type User struct {
	Student //匿名字段
}

type Student struct {
	Id   int
	Name string
}

func main() {
	u := &User{Student{
		Id:   1,
		Name: "反射",
	}}

	v := reflect.ValueOf(u)
	//修改值必须是指针类型
	if v.Kind() != reflect.Ptr {
		fmt.Println("不是指针类型,无法进行修改操作")
		return
	}
	//获取指针所指向的元素
	v = v.Elem()

	fmt.Printf("%#v\n", *u)
	name := v.FieldByName("Name")
	if name.Kind() == reflect.String {
		name.SetString("小学生")
	}
	fmt.Printf("%#v\n", *u)

	//如果是整型的话
	test := 888
	testV := reflect.ValueOf(&test)
	testV.Elem().SetInt(666)
	fmt.Println(test)
}

输出:

main.User{Student:main.Student{Id:1, Name:"反射"}}
main.User{Student:main.Student{Id:1, Name:"小学生"}}
666

通过反射调用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Student struct {
	Id   int
	Name string
}

func (s Student) Hello(msg string) {
	fmt.Println("hello, ", msg)
}

func main() {
	u := Student{
		Id:   1,
		Name: "反射",
	}
	v := reflect.ValueOf(u)

	//获取方法控制权 返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装
	method := v.MethodByName("Hello")
	//拼凑参数
	args := []reflect.Value{reflect.ValueOf("反射")}
	//调用函数
	method.Call(args)
}

输出:

hello,  反射

小结

上述详细说明了Golang的反射reflect的各种功能和用法,都附带有相应的示例,相信能够在工程应用中进行相应实践,总结一下就是:

  • 反射可以大大提高程序的灵活性,使得interface{}有更大的发挥余地
    • 反射必须结合interface才玩得转
    • 变量的type要是concrete type的(也就是interface变量)才有反射一说
  • 反射可以将“接口类型变量”转换为“反射类型对象”
    • 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息
  • 反射可以将“反射类型对象”转换为“接口类型变量
    • reflect.value.Interface().(已知的类型)
    • 遍历reflect.Type的Field获取其Field
  • 反射可以修改反射类型对象,但是其值必须是“addressable”
    • 想要利用反射修改对象状态,前提是 interface.data 是 settable,即 pointer-interface
  • 通过反射可以“动态”调用方法
  • 因为Golang本身不支持模板,因此在以往需要使用模板的场景下往往就需要使用反射(reflect)来实现

Golang的反射很慢,这个和它的API设计有关。Golang reflect慢主要有两个原因

  1. 涉及到内存分配以及后续的GC;
  2. reflect实现里面有大量的枚举,也就是for循环,比如类型之类的。

参考文章