基本介绍
- 反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)
- 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段,方法)
- 通过反射,可以改变变量的值,可以调用关联的方法
- 使用反射,需要import("reflect")
反射的应用场景
- 不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。
func bridge(funcPtr interface{}, args ...interface{})
第一个参数funcPtr 以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数
- 对结构体序列化时,如果结构体有指定Tag,也会使用到反射生成对应的字符串
反射重要的函数和概念
- reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type 类型
- reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型reflectValue 是一个结构体类型。通过reflect.Value,可以获取到关于该变量的很多信息.
- 变量、interface{}和reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。
快速入门
- 基本数据类型
package main import ( "fmt" "reflect" ) // 基本数据类型反射 func reflectTest(b interface{}) { // 通过反射获取传入的变量的 type, kind, 值 // 获取 reflect.type rType := reflect.TypeOf(b) fmt.Println("rType=", rType) // 获取reflect.Value rValue := reflect.ValueOf(b) n2 := 2 + rValue.Int() fmt.Println("n2 =", n2) fmt.Printf("rVal=%v, rVal type=%T\n", rValue, rValue) // 将rVal转成interface{} iV := rValue.Interface() //将interface{} 通过断言转成需要的类型 num2 := iV.(int) fmt.Println(num2) } func main() { // 定义一个int var num int = 100 reflectTest(num) }
运行结果:
rType= int n2 = 102 rVal=100, rVal type=reflect.Value 100
- 结构体
package main import ( "fmt" "reflect" ) // 基本数据类型反射 func reflectTest(b interface{}) { // 通过反射获取传入的变量的 type, kind, 值 // 获取 reflect.type rType := reflect.TypeOf(b) fmt.Println("rType=", rType) // 获取reflect.Value rValue := reflect.ValueOf(b) // 将rVal转成interface{} iV := rValue.Interface() fmt.Printf("iv=%v, iv type=%T\n", iV, iV) //将interface{} 通过断言转成需要的类型 stu, ok := iV.(Student) if ok { fmt.Printf("stu.Name=%v\n", stu.Name) } } type Student struct { Name string Age int } func main() { // 定义一个结构体 stu := Student{ Name: "gopher.cc", Age: 5, } reflectTest(stu) }
运行结果:
rType= main.Student iv={gopher.cc 5}, iv type=main.Student stu.Name=gopher.cc
反射的注意事项和细节
- reflect.Value.Kind, 获取的变量的类别,返回的是一个常量
- Type 和 Kind 的区别
Type是类型,Kind是类别,Type和Kind可能是相同的,也可能是不同的
比如:var num int = 10 num的Type是int,Kind也是int
比如:var stu Student stu的Type是pkg1.Student,Kind是struct - 通过反射可以让变量在interface{}和reflect.Value之间相互转换
- 使用反射的方式获取变量的值(并返回对应的类型),要求数据类型匹配,比如x是int,那么就应该使用reflect.Value(x).Int(), 而不能使用其他的,否则报panic
- 通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到reflect.Value.Elem()方法
package main import ( "fmt" "reflect" ) func reflectTest(b interface{}) { val := reflect.ValueOf(b) fmt.Printf("val type=%T\n", b) val.Elem().SetInt(20) } func main() { var num int = 10 reflectTest(&num) fmt.Println(num) }
运行结果:
val type=*int 20
最佳实践
使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
package main import ( "fmt" "reflect" ) // 定义一个Monster结构体 type Monster struct { Name string `json:"name"` Age int `json:"age"` Score float64 Sex string } // 方法,返回两个函数的和 func (m Monster) GetSum(n1, n2 int) int { return n1 + n2 } // 接收四个值,给m赋值 func (m Monster) Set(name string, age int, score float64, sex string) { m.Name = name m.Age = age m.Score = score m.Sex = sex } // 显示m的值 func (m Monster) Print() { fmt.Println("----start-------") fmt.Println(m) fmt.Println("----end-------") } func TestStruct(a interface{}) { // 获取reflect.type 类型 typ := reflect.TypeOf(a) // 获取reflect.value类型 val := reflect.ValueOf(a) // 获取a对应的类别 kd := val.Kind() // 如果传入的不是struct,就退出 if kd != reflect.Struct { fmt.Println("expect struct") return } // 获取该结构体有几个字段 num := val.NumField() fmt.Printf("struct has %d fields \n", num) //遍历结构体的字段 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) // 获取到第二个方法,并调用它 // 调用结构体的第一个方法 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()) } func main() { // 创建了一个Monster实例 var a Monster = Monster{ Name: "gopher.cc", Age: 4, Score: 90.0, } // 将Monster实例传给TestStruct 函数 TestStruct(a) }
运行结果:
struct has 4 fields Field0: 值为=gopher.cc Field0: tag 为=name Field1: 值为=4 Field1: tag 为=age Field2: 值为=90 Field3: 值为= struct has 3 methods ----start------- {gopher.cc 4 90 } ----end------- res= 50