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.14
reflectType(a) //type:float32
var b int64 = 100
reflectType(b) //type:int64
}
type MyInt int64
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Printf("%v %v\n",t.Name(),t.Kind())
}
func main() {
var a *float32
var b MyInt
var c rune
reflectType(a) //ptr
reflectType(b) //type:myInt kind:int64
reflectType(c) //type:int32 kind:int32
}
type person struct {
name string
age int
}
type book struct {
title string
}
var p=person{
name:"cheng",
age:18,
}
var b = book{title: "Golang"}
reflectType(p) // person struct
reflectType(b) // book struct
在reflect包中定义的Kind类型如下
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 // 底层指针
)
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.14
var b int64 = 100
reflectValue(a) //3.14
reflectValue(b) //100
c := 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 value
reflectSetValues(&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()) //false
b := struct {name string}{}
//b := struct {}{}
fmt.Println(reflect.ValueOf(b).FieldByName("name").IsValid()) //true
fmt.Println(reflect.ValueOf(b).MethodByName("func").IsValid()) //false
c :=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。
- 大量使用反射的代码通常难以理解。
- 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。