Go语言之反射

和Java语言同样,Go也实现运行时反射,这为咱们提供一种能够在运行时操做任意类型对象的能力。好比咱们能够查看一个接口变量的具体类型,看看一个结构体有多少字段,如何修改某个字段的值等。数组


TypeOf和ValueOf


在Go的反射定义中,任何接口都会由两部分组成的,一个是接口的具体类型,一个是具体类型对应的值。好比var i int = 3 ,由于interface{}能够表示任何类型,因此变量i能够转为interface{},因此能够把变量i当成一个接口,那么这个变量在Go反射中的表示就是<Value,Type>,其中Value为变量的值3,Type变量的为类型intide


在Go反射中,标准库为咱们提供两种类型来分别表示他们reflect.Valuereflect.Type,而且提供了两个函数来获取任意对象的ValueType函数


func main() {    u:= User{"张三",20}    t:=reflect.TypeOf(u)    fmt.Println(t)
}
type User struct{    Name string    Age int
}


reflect.TypeOf能够获取任意对象的具体类型,这里经过打印输出能够看到是main.User这个结构体型。reflect.TypeOf函数接受一个空接口interface{}做为参数,因此这个方法能够接受任何类型的对象。spa


接着上面的例子,咱们看下如何反射获取一个对象的Value指针


 v:=reflect.ValueOf(u)
    fmt.Println(v)


TypeOf函数同样,也能够接受任意对象,能够看到打印输出为{张三 20}。对于以上这两种输出,Go语言还经过fmt.Printf函数为咱们提供了简便的方法。code


    fmt.Printf("%T\n",u)
    fmt.Printf("%v\n",u)


这个例子和以上的例子中的输出同样。orm


reflect.Value转原始类型


上面的例子咱们能够经过reflect.ValueOf函数把任意类型的对象转为一个reflect.Value,那咱们若是咱们想逆向转过回来呢,其实也是能够的,reflect.Value为咱们提供了Inteface方法来帮咱们作这个事情。继续接上面的例子:对象


    u1:=v.Interface().(User)
    fmt.Println(u1)


这样咱们就又还原为原来的User对象了,经过打印的输出就能够验证。这里能够还原的缘由是由于在Go的反射中,把任意一个对象分为reflect.Valuereflect.Type,而reflect.Value又同时持有一个对象的reflect.Valuereflect.Type,因此咱们能够经过reflect.ValueInterface方法实现还原。如今咱们看看如何从一个reflect.Value获取对应的reflect.Type索引



  t1:=v.Type()
    fmt.Println(t1)


如上例中,经过reflect.ValueType方法就能够得到对应的reflect.Type接口


获取类型底层类型


底层的类型是什么意思呢?其实对应的主要是基础类型,接口、结构体、指针这些,由于咱们能够经过type关键字声明不少新的类型,好比上面的例子,对象u的实际类型是User,可是对应的底层类型是struct这个结构体类型,咱们来验证下。


fmt.Println(t.Kind())


经过Kind方法便可获取,很是简单,固然咱们也可使用Value对象的Kind方法,他们是等价的。


Go语言提供了如下这些最底层的类型,能够看到,都是最基本的。


const (    Invalid Kind = iota    
   Bool    Int    Int8    Int16    Int32    Int64    Uint    Uint8    Uint16    Uint32    Uint64    Uintptr    Float32    Float64    Complex64    Complex128    Array    Chan    Func    Interface    Map    Ptr    Slice    String    Struct    UnsafePointer
)


遍历字段和方法


经过反射,咱们能够获取一个结构体类型的字段,也能够获取一个类型的导出方法,这样咱们就能够在运行时了解一个类型的结构,这是一个很是强大的功能。



for i:=0;i<t.NumField();i++ {
        fmt.Println(t.Field(i).Name)
    }    
    for i:=0;i<t.NumMethod() ;i++  {
        fmt.Println(t.Method(i).Name)
    }


这个例子打印出结构体的全部字段名以及该结构体的方法。NumField方法获取结构体有多少个字段,而后经过Field方法传递索引的方式,循环获取每个字段,而后打印出他们的名字。


一样的对于方法也相似,这里再也不赘述。


修改字段的值


假如咱们想在运行中动态的修改某个字段的值有什么办法呢?一种就是咱们常规的有提供的方法或者导出的字段能够供咱们修改,还有一种是使用反射,这里主要介绍反射。


func main() {    x:=2    v:=reflect.ValueOf(&x)    v.Elem().SetInt(100)    fmt.Println(x)
}


以上就是经过反射修改一个变量的例子。


由于reflect.ValueOf函数返回的是一份值的拷贝,因此前提是咱们是传入要修改变量的地址。


其次须要咱们调用
Elem方法找到这个指针指向的值。


最后咱们就可使用
SetInt方法修改值了。


以上有几个重点,才能够保证值能够被修改,Value为咱们提供了CanSet方法能够帮助咱们判断是否能够修改该对象。


咱们如今能够更新变量的值了,那么如何修改结构体字段的值呢?你们本身试试。


动态调用方法


结构体的方法咱们不光能够正常的调用,还可使用反射进行调用。要想反射调用,咱们先要获取到须要调用的方法,而后进行传参调用,以下示例:


func main() {    u:=User{"张三",20}    v:=reflect.ValueOf(u)    mPrint:=v.MethodByName("Print")    args:=[]reflect.Value{reflect.ValueOf("前缀")}    fmt.Println(mPrint.Call(args))
}
type User struct{    Name string    Age int
}
func (u User) Print(prfix string){    fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age)
}


MethodByName方法可让咱们根据一个方法名获取一个方法对象,而后咱们构建好该方法须要的参数,最后调用Call就达到了动态调用方法的目的。


获取到的方法咱们可使用IsValid 来判断是否可用(存在)。


这里的参数是一个Value类型的数组,因此须要的参数,咱们必需要经过ValueOf函数进行转换。

相关文章
相关标签/搜索