go-反射
反射的基本介绍
1、反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
2、如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
3、通过反射,可以修改变量的值,可以调用关联的方法。
4、使用反射,需要 import (“reflect”)
在Go语言的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。 在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Type和reflect.Value两部分组成,并且reflect包提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的Value和Type。
反射的应用场景
反射常见的应用场景有以下两种:
1、不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法进行反射.
2、对结构体序列化时,如果结构体有指定tag,也会使用到反射生成对应的字符串
package main import ( "encoding/json" "fmt" ) type Monster struct { Name string `json:"monsterName"` Age int `json:"monsterAge"` } func main() { m := Monster{ Name: "bingle", Age: 18, } data, _ := json.Marshal(m) fmt.Println("json result : ", string(data)) }
反射重要的函数和概念
1、reflect.TypeOf(变量名),获取变量的类型,返回 reflect.Type 类型
2、reflect.ValueOf(变量名),获取变量的值,返回reflect.Value 类型,reflect.Value 是一个结构体类型。通过reflect.Value ,可以获取到该变量的很多信息。
3、变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。
反射的注意事项和细节
1、reflect.Value.Kind,获取变量的类别,返回的是一个常量
在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、Type 和 Kind 的区别
Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的.
比如:var num int = 10 num 的 Type 是 int , Kind 也是 int
比如:var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct
3、通过反射,可以让变量在 interface{} 和 Reflect.Value 之间进行转换,
4、使用反射的方式来获取变量的值(并发挥对应的类型),要求数据类型匹配
比如,x 是 int ,那么就应该使用 reflect.Value(x).Int(),而不能使用其他的,否则会报 panic
5、通过反射的来修改变量,想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。
注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到 reflect.Value.Elem()方法
package main import ( "fmt" "reflect" ) func testInt(b interface{}) { val := reflect.ValueOf(b) fmt.Printf("val type = %T \n", val) val.Elem().SetInt(110) fmt.Printf("val = %v \n", val) } func main() { var num int = 20 testInt(&num) fmt.Println("num = ", num) }
上段代码,执行结果如下:
反射的实践
1、使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
package main import ( "fmt" "reflect" ) //定义了一个 Person 结构体 type Person struct { Name string `json:"name"` Age int `json:"monster_age"` } //方法,返回两个数的和 func (p Person) GetSum(n1, n2 int) int { return n1 + n2 } //方法, 接收两个值,给 p 赋值 func (p Person) Set(name string, age int) { p.Name = name p.Age = age } //方法,显示 p 的值 func (p Person) Print() { fmt.Println("---start~----") fmt.Println(p) fmt.Println("---end~----") } func TestStruct(a interface{}) { // 获取 reflect.Type 类型 typ := reflect.TypeOf(a) // 获取 reflect.Value 类型 val := reflect.ValueOf(a) // 获取到 a 对应的类别 kind := val.Kind() // 如果传入的不是 struct,就退出 if kind != reflect.Struct { fmt.Println("expect struct") return } // 获取到该结构体有几个字段 num := val.NumField() fmt.Printf("struct has %d fields\n", num) //2 // 变量结构体的所有字段 for i := 0; i < num; i++ { fmt.Printf("Field %d: 值为=%v\n", i, val.Field(i)) // 获取到 struct 标签, 注意需要通过 reflect.Type 来获取 tag 标签的值 tagVal := typ.Field(i).Tag.Get("json") // 如果该字段于 tag 标签就显示,否则就不显示 if tagVal!="" { fmt.Printf("Field %d: tag 为=%v\n", i, tagVal) } } // 获取到该结构体有多少个方法 numOfMethod := val.NumMethod() fmt.Printf("struct has %d methods\n", numOfMethod) // var params []reflect.Value // 方法的排序默认是按照 函数名的排序(ASCII 码) val.Method(1).Call(nil) // 获取到第二个方法。调用它 // 调用结构体的第 1 个方法 Method(0) var params []reflect.Value // 声明了 []reflect.Value params = append(params,reflect.ValueOf(10)) params = append(params,reflect.ValueOf(40)) res := val.Method(0).Call(params) // 传入的参数是 []reflect.Value, 返回[]reflect.Value fmt.Println("res=", res[0].Int()) //返回结果, 返回的结果是 []reflect.Value*/ } func main() { // 创建了一个 Person 实例 var person Person =Person{ Name: "bingle", Age: 18, } // 将 Person 实例传递给 TestStruct 函数 TestStruct(person) }
ValueOf
reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。
reflect.Value类型提供的获取原始值的方法如下:
isNil()和isValid()
isNil()
func (v Value) IsNil() bool
IsNil()报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。
isValid()
func (v Value) IsValid() bool
IsValid()返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。
举个例子:
IsNil()常被用于判断指针是否为空;IsValid()常被用于判定返回值是否有效。
func main() { // *int类型空指针 var a *int fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil()) // nil值 fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid()) // 实例化一个匿名结构体 b := struct{}{} // 尝试从结构体中查找"abc"字段 fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid()) // 尝试从结构体中查找"abc"方法 fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid()) // map c := map[string]int{} // 尝试从map中查找一个不存在的键 fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("bingle")).IsValid()) }
与结构体相关的方法
任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()和Field()方法获得结构体成员的详细信息。
reflect.Type
中与获取结构体成员相关的的方法如下所示。
反射是把双刃剑
反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
1、基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
2、大量使用反射的代码通常难以理解。
3、反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。