go中的关键字-reflect 反射
1. 什么是反射
Golang提供了一种机制,在编译时不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射。
2. 反射的使用
2.1 获取变量内部信息
reflect提供了两种类型来进行访问接口变量的内容:
类型reflect.ValueOf() 的作用是:获取输入参数接口中的数据的值,如果为空则返回0 <- 注意是0。
类型reflect.TypeOf() 动态获取输入参数接口中的值的类型,如果为空则返回nil <- 注意是nil。
看示例:
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func main() { 9 var name string = "编程菜菜" 10 11 //TypeOf会返回目标数据的类型,比如int/float/struct/指针等 12 reflectType := reflect.TypeOf(name) 13 14 //valueOf会返回目标数据的值,比如上文的“编程菜菜” 15 reflectValue := reflect.ValueOf(name) 16 17 fmt.Println("type:", reflectType) 18 fmt.Println("value:", reflectValue) 19 }
输出结果:
1 type: string 2 value: 编程菜菜
在以上操作发生的时候,反射将“接口类型的变量”转为了“反射的接口类型的变量”,比如上文实际上返回的是reflect.Value和reflect.Type的接口对象。
2.2 struct的反射
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func (s Student) Hello(){ 14 fmt.Println("我是一个学生") 15 } 16 17 func main() { 18 s := Student{Id: 1, Name: "编程菜菜"} 19 20 t := reflect.TypeOf(s) // 获取目标对象 21 22 fmt.Println("类型的名称是: ", t.Name()) // .Name()可以获取去这个类型的名称 23 24 v := reflect.ValueOf(s) // 获取目标对象的值类型 25 26 for i := 0; i < t.NumField(); i++ { // .NumField()来获取其包含的字段的总数目 27 28 key := t.Field(i) // 从0开始获取Student所包含的key 29 30 value := v.Field(i).Interface() // 通过interface方法来获取key所对应的值 31 32 fmt.Printf("第%d个字段是:%s:%v = %v \n", i+1, key.Name, key.Type, value) 33 } 34 35 for i:=0;i<t.NumMethod(); i++ { // 通过.NumMethod()来获取Student里头的方法 36 m := t.Method(i) 37 fmt.Printf("第%d个方法是:%s:%v\n", i+1, m.Name, m.Type) 38 } 39 }
1 这个类型的名称是: Student 2 第1个字段是:Id:int = 1 3 第2个字段是:Name:string = 编程菜菜 4 第1个方法是:Hello:func(main.Student)
2.3 判断传入的类型是否是我们想要的类型
1 package main 2 3 import ( 4 "reflect" 5 "fmt" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func main() { 14 s := Student{Id: 1, Name: "编程菜菜"} 15 t := reflect.TypeOf(s) 16 17 // 通过.Kind()来判断对比的值是否是struct类型 18 if k := t.Kind(); k == reflect.Struct { 19 fmt.Println("yes") 20 } 21 22 num := 1; 23 numType := reflect.TypeOf(num) 24 if k := numType.Kind(); k == reflect.Int { 25 fmt.Println("yes") 26 } 27 }
1 yes 2 yes
2.4 通过反射修改内容
1 package main 2 3 import ( 4 "reflect" 5 "fmt" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func main() { 14 s := &Student{Id: 1, Name: "编程菜菜"} 15 16 v := reflect.ValueOf(s) 17 18 if v.Kind() != reflect.Ptr { // 修改值必须是指针类型否则不可行 19 fmt.Println("不是指针类型,没法进行修改操作") 20 return 21 } 22 23 v = v.Elem() // 获取指针所指向的元素 24 25 name := v.FieldByName("Name") // 获取目标key的Value的封装 26 27 if name.Kind() == reflect.String { 28 name.SetString("小学生") 29 } 30 31 fmt.Printf("%#v \n", *s) 32 33 test := 888 // 如果是整型的话 34 testV := reflect.ValueOf(&test) 35 testV.Elem().SetInt(666) 36 fmt.Println(test) 37 }
输出结果:
1 main.Student{Id:1, Name:"小学生"} 2 666
2.5 通过反射调用方法
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func (s Student) EchoName(name string){ 14 fmt.Println("我的名字是:", name) 15 } 16 17 func main() { 18 s := Student{Id: 1, Name: "咖啡色的羊驼"} 19 20 v := reflect.ValueOf(s) 21 22 // 获取方法控制权 23 // 官方解释:返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装 24 mv := v.MethodByName("EchoName") 25 26 args := []reflect.Value{reflect.ValueOf("编程菜菜")} // 拼凑参数 27 28 mv.Call(args) // 调用函数 29 }
1 我的名字是: 编程菜菜
使用规则:
1. 使用反射时需要先确定要操作的值是否是期望的类型,是否是可以进行“赋值”操作的,否则reflect包将会毫不留情的产生一个panic。
2. 反射主要与Golang的interface类型相关,只有interface类型才有反射一说。看下TypeOf和ValueOf,会发现其实传入参数的时候已经被转为接口类型了。
3. reflect有关的部分源码分析
1 // 部分源代码 2 func TypeOf(i interface{}) Type { 3 eface := *(*emptyInterface)(unsafe.Pointer(&i)) 4 return toType(eface.typ) 5 } 6 7 func ValueOf(i interface{}) Value { 8 if i == nil { 9 return Value{} 10 } 11 escapes(i) 12 13 return unpackEface(i) 14 }
TypeOf函数动态获取输入参数接口中的值的类型,如果接口为空则返回nil。
转换为emptyinterface,emptyInterface是interface {}值的标头。
1 // emptyInterface is the header for an interface{} value. 2 // emptyInterface是interface {}值的标头。 3 type emptyInterface struct { 4 typ *rtype 5 word unsafe.Pointer 6 }
rtype结构体,实现了Type接口
size:
存储这个类型的一个值所需要的字节数(值占用的字节数)
algin:
这个类型的一个变量在内存中的对齐后的所用的字节数 (变量占的字节数)
FieldAlign
: 这种类型的变量如果是struct中的字段,那么它对齐后所用的字节数
1 // rtype是大多数值的通用实现。 2 //它嵌入在其他结构类型中。 3 // 4 // rtype必须与../runtime/type.go:/^type._type保持同步。 5 type rtype struct { 6 size uintptr 7 ptrdata uintptr // 类型中可以包含指针的字节数 8 hash uint32 // hash of type; avoids computation in hash tables 9 tflag tflag // 类型的哈希; 避免在哈希表中进行计算 10 align uint8 // 变量与此类型的对齐 11 fieldAlign uint8 // 结构域与该类型的对齐 12 kind uint8 // C的枚举 13 alg *typeAlg // 算法表 14 gcdata *byte // 垃圾收集数据 15 str nameOff // 字符串形式 16 ptrToThis typeOff // 指向此类型的指针的类型,可以为零 17 }
Value
Value描述对象的值信息,并不是所有的方法对任何的类型都有意义,特定的方法只适用于特定的类型。
1 type Value struct { 2 // typ包含由值表示的值的类型。 3 typ *rtype 4 5 // 指针值的数据;如果设置了flagIndir,则为数据的指针。 6 //在设置flagIndir或typ.pointers()为true时有效。 7 ptr unsafe.Pointer 8 9 // 标志保存有关该值的元数据。 10 //最低位是标志位: 11 //-flagStickyRO:通过未导出的未嵌入字段获取,因此为只读 12 //-flagEmbedRO:通过未导出的嵌入式字段获取,因此为只读 13 //-flagIndir:val保存指向数据的指针 14 //-flagAddr:v.CanAddr为true(表示flagIndir) 15 //-flagMethod:v是方法值。 16 //接下来的五位给出值的种类。 17 //重复typ.Kind(),方法值除外。 18 //其余的23+位给出方法值的方法编号。 19 //如果flag.kind()!= Func,则代码可以假定flagMethod未设置。 20 //如果是ifaceIndir(typ),则代码可以假定设置了flagIndir。 21 flag 22 23 //方法值代表一个经过咖喱的方法调用像r.Read为某些接收者r。 typ + val + flag位描述接收者r,但标志的Kind位表示Func(方法是函数),并且标志的高位给出方法号在r的类型的方法表中。 24 }
总结
- 涉及到内存分配以及后续的GC
- reflect实现里面有大量的枚举,也就是for循环,比如类型之类的