学习使用Go反射的用法示例
什么是反射
大多数时候,Go中的变量,类型和函数非常简单直接。当需要一个类型、变量或者是函数时,可以直接定义它们:
typeFoostruct{
Aint
Bstring
}
varxFoo
funcDoSomething(fFoo){
fmt.Println(f.A,f.B)
}
但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。它还允许您在运行时检查,修改和创建变量,函数和结构体。
Go中的反射是基于三个概念构建的:类型,种类和值(TypesKindsValues)。标准库中的reflect包提供了Go反射的实现。
反射变量类型
首先让我们看一下类型。你可以使用反射来调用函数varType:=reflect.TypeOf(var)来获取变量var的类型。这将返回类型为reflect.Type的变量,该变量具有获取定义时变量的类型的各种信息的方法集。下面我们来看一下常用的获取类型信息的方法。
我们要看的第一个方法是Name()。这将返回变量类型的名称。某些类型(例如切片或指针)没有名称,此方法会返回空字符串。
下一个方法,也是我认为第一个真正非常有用的方法是Kind()。Type是由Kind组成的---Kind是切片,映射,指针,结构,接口,字符串,数组,函数,int或其他某种原始类型的抽象表示。要理解Type和Kind之间的差异可能有些棘手,但是请你以这种方式来思考。如果定义一个名为Foo的结构体,则Kind为struct,类型为Foo。
使用反射时要注意的一件事:反射包中的所有内容都假定你知道自己在做什么,并且如果使用不正确,许多函数和方法调用都会引起panic。例如,如果你在reflect.Type上调用与当前类型不同的类型关联的方法,您的代码将会panic。
如果变量是指针,映射,切片,通道或数组变量,则可以使用varType.Elem()找出指向或包含的值的类型。
如果变量是结构体,则可以使用反射来获取结构体中的字段数,并从每个字段上获取reflect.StructField结构体。reflection.StructField为您提供了字段的名称,标号,类型和结构体标签。其中标签信息对应reflect.StructTag类型的字符串,并且它提供了Get方法用于解析和根据特定key提取标签信息中的子串。
下面是一个简单的示例,用于输出各种变量的类型信息:
typeFoostruct{
Aint`tag1:"FirstTag"tag2:"SecondTag"`
Bstring
}
funcmain(){
sl:=[]int{1,2,3}
greeting:="hello"
greetingPtr:=&greeting
f:=Foo{A:10,B:"Salutations"}
fp:=&f
slType:=reflect.TypeOf(sl)
gType:=reflect.TypeOf(greeting)
grpType:=reflect.TypeOf(greetingPtr)
fType:=reflect.TypeOf(f)
fpType:=reflect.TypeOf(fp)
examiner(slType,0)
examiner(gType,0)
examiner(grpType,0)
examiner(fType,0)
examiner(fpType,0)
}
funcexaminer(treflect.Type,depthint){
fmt.Println(strings.Repeat("\t",depth),"Typeis",t.Name(),"andkindis",t.Kind())
switcht.Kind(){
casereflect.Array,reflect.Chan,reflect.Map,reflect.Ptr,reflect.Slice:
fmt.Println(strings.Repeat("\t",depth+1),"Containedtype:")
examiner(t.Elem(),depth+1)
casereflect.Struct:
fori:=0;i
变量的类型输出如下:
Typeisandkindisslice
Containedtype:
Typeisintandkindisint
Typeisstringandkindisstring
Typeisandkindisptr
Containedtype:
Typeisstringandkindisstring
TypeisFooandkindisstruct
Field1nameisAtypeisintandkindisint
Tagistag1:"FirstTag"tag2:"SecondTag"
tag1isFirstTagtag2isSecondTag
Field2nameisBtypeisstringandkindisstring
Typeisandkindisptr
Containedtype:
TypeisFooandkindisstruct
Field1nameisAtypeisintandkindisint
Tagistag1:"FirstTag"tag2:"SecondTag"
tag1isFirstTagtag2isSecondTag
Field2nameisBtypeisstringandkindisstring
Runingoplayground:https://play.golang.org/p/lZ97yAUHxX
使用反射创建新实例
除了检查变量的类型外,还可以使用反射来读取,设置或创建值。首先,需要使用refVal:=reflect.ValueOf(var)为变量创建一个reflect.Value实例。如果希望能够使用反射来修改值,则必须使用refPtrVal:=reflect.ValueOf(&var);获得指向变量的指针。如果不这样做,则可以使用反射来读取该值,但不能对其进行修改。
一旦有了reflect.Value实例就可以使用Type()方法获取变量的reflect.Type。
如果要修改值,请记住它必须是一个指针,并且必须首先对其进行解引用。使用refPtrVal.Elem().Set(newRefVal)来修改值,并且传递给Set()的值也必须是reflect.Value。
如果要创建一个新值,可以使用函数newPtrVal:=reflect.New(varType)来实现,并传入一个reflect.Type。这将返回一个指针值,然后可以像上面那样使用Elem().Set()对其进行修改。
最后,你可以通过调用Interface()方法从reflect.Value回到普通变量值。由于Go没有泛型,因此变量的原始类型会丢失;该方法返回类型为interface{}的值。如果创建了一个指针以便可以修改该值,则需要使用Elem().Interface()解引用反射的指针。在这两种情况下,都需要将空接口转换为实际类型才能使用它。
下面的代码来演示这些概念:
typeFoostruct{
Aint`tag1:"FirstTag"tag2:"SecondTag"`
Bstring
}
funcmain(){
greeting:="hello"
f:=Foo{A:10,B:"Salutations"}
gVal:=reflect.ValueOf(greeting)
//notapointersoallwecandoisreadit
fmt.Println(gVal.Interface())
gpVal:=reflect.ValueOf(&greeting)
//it'sapointer,sowecanchangeit,anditchangestheunderlyingvariable
gpVal.Elem().SetString("goodbye")
fmt.Println(greeting)
fType:=reflect.TypeOf(f)
fVal:=reflect.New(fType)
fVal.Elem().Field(0).SetInt(20)
fVal.Elem().Field(1).SetString("Greetings")
f2:=fVal.Elem().Interface().(Foo)
fmt.Printf("%+v,%d,%s\n",f2,f2.A,f2.B)
}
他们的输出如下:
hello
goodbye
{A:20B:Greetings},20,Greetings
Runingoplaygroundhttps://play.golang.org/p/PFcEYfZqZ8
反射创建引用类型的实例
除了生成内置类型和用户定义类型的实例之外,还可以使用反射来生成通常需要make函数的实例。可以使用reflect.MakeSlice,reflect.MakeMap和reflect.MakeChan函数制作切片,Map或通道。在所有情况下,都提供一个reflect.Type,然后获取一个reflect.Value,可以使用反射对其进行操作,或者可以将其分配回一个标准变量。
funcmain(){
//定义变量
intSlice:=make([]int,0)
mapStringInt:=make(map[string]int)
//获取变量的reflect.Type
sliceType:=reflect.TypeOf(intSlice)
mapType:=reflect.TypeOf(mapStringInt)
//使用反射创建类型的新实例
intSliceReflect:=reflect.MakeSlice(sliceType,0,0)
mapReflect:=reflect.MakeMap(mapType)
//将创建的新实例分配回一个标准变量
v:=10
rv:=reflect.ValueOf(v)
intSliceReflect=reflect.Append(intSliceReflect,rv)
intSlice2:=intSliceReflect.Interface().([]int)
fmt.Println(intSlice2)
k:="hello"
rk:=reflect.ValueOf(k)
mapReflect.SetMapIndex(rk,rv)
mapStringInt2:=mapReflect.Interface().(map[string]int)
fmt.Println(mapStringInt2)
}
使用反射创建函数
反射不仅仅可以为存储数据创造新的地方。还可以使用reflect.MakeFunc函数使用reflect来创建新函数。该函数期望我们要创建的函数的reflect.Type,以及一个闭包,其输入参数为[]reflect.Value类型,其返回类型也为[]reflect.Value类型。下面是一个简单的示例,它为传递给它的任何函数创建一个定时包装器:
funcMakeTimedFunction(finterface{})interface{}{
rf:=reflect.TypeOf(f)
ifrf.Kind()!=reflect.Func{
panic("expectsafunction")
}
vf:=reflect.ValueOf(f)
wrapperF:=reflect.MakeFunc(rf,func(in[]reflect.Value)[]reflect.Value{
start:=time.Now()
out:=vf.Call(in)
end:=time.Now()
fmt.Printf("calling%stook%v\n",runtime.FuncForPC(vf.Pointer()).Name(),end.Sub(start))
returnout
})
returnwrapperF.Interface()
}
functimeMe(){
fmt.Println("starting")
time.Sleep(1*time.Second)
fmt.Println("ending")
}
functimeMeToo(aint)int{
fmt.Println("starting")
time.Sleep(time.Duration(a)*time.Second)
result:=a*2
fmt.Println("ending")
returnresult
}
funcmain(){
timed:=MakeTimedFunction(timeMe).(func())
timed()
timedToo:=MakeTimedFunction(timeMeToo).(func(int)int)
fmt.Println(timedToo(2))
}
你可以在goplayground运行代码https://play.golang.org/p/QZ8ttFZzGx并看到输出如下:
starting
ending
callingmain.timeMetook1s
starting
ending
callingmain.timeMeTootook2s
4
反射是每个Go开发人员都应了解并学会的强大工具。但是使用他们可以用来做什么呢?在下一篇博客文章中,我将探讨现有库中反射的一些用法,并使用反射来创建一些新的东西。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。