和Java语言同样,Go也实现运行时反射,这为咱们提供一种能够在运行时操做任意类型对象的能力。好比咱们能够查看一个接口变量的具体类型,看看一个结构体有多少字段,如何修改某个字段的值等。数组
在Go的反射定义中,任何接口都会由两部分组成的,一个是接口的具体类型,一个是具体类型对应的值。好比var i int = 3 ,由于interface{}能够表示任何类型,因此变量i能够转为interface{},因此能够把变量i当成一个接口,那么这个变量在Go反射中的表示就是<Value,Type>,其中Value为变量的值3,Type变量的为类型int。ide
在Go反射中,标准库为咱们提供两种类型来分别表示他们reflect.Value和reflect.Type,而且提供了两个函数来获取任意对象的Value和Type。函数
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.ValueOf函数把任意类型的对象转为一个reflect.Value,那咱们若是咱们想逆向转过回来呢,其实也是能够的,reflect.Value为咱们提供了Inteface方法来帮咱们作这个事情。继续接上面的例子:对象
u1:=v.Interface().(User) fmt.Println(u1)
这样咱们就又还原为原来的User对象了,经过打印的输出就能够验证。这里能够还原的缘由是由于在Go的反射中,把任意一个对象分为reflect.Value和reflect.Type,而reflect.Value又同时持有一个对象的reflect.Value和reflect.Type,因此咱们能够经过reflect.Value的Interface方法实现还原。如今咱们看看如何从一个reflect.Value获取对应的reflect.Type。索引
t1:=v.Type() fmt.Println(t1)
如上例中,经过reflect.Value的Type方法就能够得到对应的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函数进行转换。