基本介绍
- 反射可以在运行时动态获取变量的各种信息,比如变量的类型(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