1.反射介绍
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go程序在运行期使用reflect包访问程序的反射信息。
2.reflect包
在Go语言的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。 在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Type和reflect.Value两部分组成,
并且reflect包提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的Value和Type。
2.1TypeOf
使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type)
import ("fmt""reflect")func reflectType(x interface{}) {v := reflect.TypeOf(x)fmt.Printf("%v\n",v)}func main() {var a float32 = 3.14reflectType(a) //type:float32var b int64 = 100reflectType(b) //type:int64}
type MyInt int64func reflectType(x interface{}) {t := reflect.TypeOf(x)fmt.Printf("%v %v\n",t.Name(),t.Kind())}func main() {var a *float32var b MyIntvar c runereflectType(a) //ptrreflectType(b) //type:myInt kind:int64reflectType(c) //type:int32 kind:int32}
type person struct {name stringage int}type book struct {title string}var p=person{name:"cheng",age:18,}var b = book{title: "Golang"}reflectType(p) // person structreflectType(b) // book struct
在reflect包中定义的Kind类型如下
type Kind uintconst (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 // 底层指针)
2.2ValueOf
通过反射获取值
func reflectValue(x interface{}) {v:=reflect.ValueOf(x)k:=v.Kind()switch k {case reflect.Int64:fmt.Println(int64(v.Int()))case reflect.Float32:fmt.Println(float32(v.Float()))case reflect.Float64:fmt.Println(float64(v.Float()))}}
var a float32 = 3.14var b int64 = 100reflectValue(a) //3.14reflectValue(b) //100c := reflect.ValueOf(10)fmt.Printf("%T %v",c,c) //reflect.value 10
通过反射设置变量的值
import ("fmt""reflect")func reflectSetValue(x interface{}) {v := reflect.ValueOf(x)if v.Kind() == reflect.Int64{v.SetInt(200) //修改的是副本}}func reflectSetValues(x interface{}) {v := reflect.ValueOf(x)//反射中使用 Elem()方法获取指针对应的值if v.Elem().Kind() == reflect.Int64{v.Elem().SetInt(200)}}func main() {var a int64 = 100//reflectSetValue(a) panic: reflect: reflect.Value.SetInt using unaddressable valuereflectSetValues(&a)fmt.Println(a)}
isNil()
IsNil()常被用于判断指针是否为空
func (v Value) IsNil() bool
isValid()
IsValid()常被用于判定返回值是否有效
func (v Value) IsValid() bool
var a *int //*int类型空指针//IsNil()常被用于判断指针是否为空fmt.Println(reflect.ValueOf(a).IsNil()) //true//IsValid()常被用于判定返回值是否有效fmt.Println(reflect.ValueOf(nil).IsValid()) //falseb := struct {name string}{}//b := struct {}{}fmt.Println(reflect.ValueOf(b).FieldByName("name").IsValid()) //truefmt.Println(reflect.ValueOf(b).MethodByName("func").IsValid()) //falsec :=map[string]int{}fmt.Println(reflect.ValueOf(c).MapIndex(reflect.ValueOf("name")).IsValid()) //false
3.结构体反射
当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息。
type student struct {Name string `json:"name"`Score int `json:"score"`}func main() {stu :=student{Name: "cheng",Score: 90,}t := reflect.TypeOf(stu)fmt.Println(t.Name(),t.Kind())for i := 0; i < t.NumField(); i++ {field := t.Field(i)fmt.Printf("name:%s\n",field.Name)fmt.Printf("name:%d\n",field.Index)fmt.Printf("name:%v\n",field.Type)fmt.Printf("name:%v\n",field.Tag.Get("json"))}}
4.反射是把双刃剑
反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic。
- 大量使用反射的代码通常难以理解。
- 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。
