简介
Golang提供了一种机制,在编译时不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射。
为什么使用反射?
打个比方,有时候我们需要一个函数可以处理各种类型的值。在不知道类型的情况下,你可能会这么写:
switch value.(type) {
case string:
// ...一些操作
case int:
// ...一些操作
case cbsStruct: // 自定义的结构体
// ...一些操作
// ...
}
这边存在一个问题:类型很多,这个函数会写的非常长,而且还可能存在自定的类型,也就是说这个判断日后可能还要一直改,因为无法知道未知值到底属于什么类型。
无法透视一个未知类型的时候,以上代码其实不是很合理,这时候就需要有反射来帮忙你处理,反射使用TypeOf和ValueOf函数从接口中获取目标对象的信息,轻松完成目的。
Type和Value
类型 | 作用 |
---|---|
reflect.ValueOf() | 获取输入参数接口中的数据的值,如果为空则返回0 <- 注意是0 |
reflect.TypeOf() | 动态获取输入参数接口中的值的类型,如果为空则返回nil <- 注意是nil |
类型之间的转换
int例子
func reflect1(num interface{}){
rtype :=reflect.TypeOf(num)//获取到reflect.Type
fmt.Printf("%v-%T\n",rtype,rtype)//int-*reflect.rtype,注意这不是基本数据类型中的int
rval :=reflect.ValueOf(num)//获取到reflect.Value
fmt.Printf("%v-%T\n",rval,rval)//100-reflect.Value
//rval+=2 编译不通过,rval的类型是reflect.Value,并不是int
n:=rval.Int()+2
fmt.Println(n)//102
//将reflect.Value转为空接口
iv :=rval.Interface()
fmt.Printf("%v-%T\n",iv,iv)//100-int,空接口陷阱,运行时知道iv是int类型,但编译时不知
//将接口通过断言,转为自己需要的类型
num2:=iv.(int)
fmt.Println(fmt.Printf("%v-%T\n",num2,num2))//100-int
}
func main() {
var num int =100
reflect1(num)
}
struct例子
type Student struct {
Name string
Age int
}
func reflect2(stu interface{}){
rval :=reflect.ValueOf(stu)
iv :=rval.Interface()//{jack 20}-main.Studentjack
//iv.Name,编译出错,空接口陷阱
fmt.Printf("%v-%T\n",iv,iv)
student:=iv.(Student)
fmt.Println(student.Name)//jack
}
func main() {
stu :=Student{"jack",20}
reflect2(stu)
}
通过反射改变的值
int例子
func demo2test1(num interface{}) {
rv :=reflect.ValueOf(num)
fmt.Println(rv.Kind())
rv.Elem().SetInt(100)
}
func main() {
var num = 1
demo2test1(&num)//这里要传地址
fmt.Println(num)
}
struct例子
type Person struct {
Name string
Age int
}
//通过反射,改变struct的值
func demo2test2(p interface{}) {
rv :=reflect.ValueOf(p)
fmt.Println(rv.Elem().Kind())
rv.Elem().FieldByName("Name").SetString("李四")
}
func main() {
p:=Person{"张三",20}
demo2test2(&p)
fmt.Println(p)
}
通过反射调用方法
type Human struct {
Name string `tag:"name"`
Age int
Sex string
}
func(this Human) String(){
fmt.Println(this.Name,this.Age,this.Sex)
}
func(this Human) Getnum(i int,n int) int{
return i+n
}
func test(human interface{}){
rtype :=reflect.TypeOf(human)
rval :=reflect.ValueOf(human)
kd :=rval.Kind()
if kd !=reflect.Struct{
panic("the value must be struct")
}
//拿到该结构有多少字段并遍历
filednum :=rval.NumField()
for i:=0;i<filednum;i++{
fmt.Printf("第%v个字段的值为:%v\n",i,rval.Field(i))
//获取标签,并显示
tag :=rtype.Field(i).Tag.Get("tag")
if tag!=""{
fmt.Printf("第%v个字段的标签为:%v\n",i,tag)
}
}
//拿到该结构有多少方法
methodnum:=rval.NumMethod()
fmt.Printf("该结构体有%v个方法\n",methodnum)
//调用方法
//注意这里的方法下标不是方法的顺序,是方法名按ASCII码排序的下标,这里调的是String
rval.Method(1).Call(nil)
//rval.MethodByName("string").Call(nil)
//调用Getnum
var params []reflect.Value
params=append(params,reflect.ValueOf(1))
params=append(params,reflect.ValueOf(2))
res :=rval.Method(0).Call(params)//参数与返回值都要是[]reflect.Value
fmt.Println(res[0].Int())
}
func main() {
h:=Human{"张三",20,"男"}
test(h)
}