QXQZX's Blog

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

反射的用途

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

为什么用反射

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

为何需要反射?

使用反射

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

First HeaderSecond 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)
}

输出:

1
2
3
4
这个类型的名称是: 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))
}

输出:

1
2
3
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
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)
}

输出:

1
2
3
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)
}

输出:

1
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循环,比如类型之类的。

参考文章


 评论