本文转载自https://github.com/KeKe-Li/For-learning-Go-Tutorial/edit/master/src/chapter07/01.0.md
For-learning-Go-Tutorial
Go语言是谷歌2009发布的第二款开源编程语言
Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
因而一直想的是自己可以根据自己学习和使用Go语言编程的心得,写一本Go的书可以帮助想要学习Go语言的初学者快速入门开发和使用!
反射(reflection)
在运行时反射是程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。它同时也是造成迷惑的来源。反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法。这对于没有源代码的包尤其有用。
golang实现反射是通过reflect包来实现的, 让原本是静态类型的go具备了很多动态类型语言的特征。reflect包有两个数据类型,一个是Type,一个是Value。它定义了两个重要的类型,Type和Value. Type就是定义的类型的一个数据类型,Value是值的类型, TypeOf和ValueOf是获取Type和Value的方法。一个Type表示一个Go类型.它是一个接口, 有许多方法来区分类型以及检查它们的组成部分, 例如一个结构体的成员或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的类型描述信息,也正是这个实体标识了接口值的动态类型.
接着我们开始我们使用Golang反射,通常在使用到Golang反射的时候会有三种定律:
- 反射定律一:反射可以将“接口类型变量”转换为“反射类型对象”.
这里的反射类型指的是reflect.Type和reflect.Value.将接口类型变量转换为反射类型变量,是通过reflect包的TypeOf和ValueOf实现的。
下面我们来看看在reflect包中的TypeOf和ValueOf的定义:
TypeOf
#TypeOf returns the reflection Type that represents the dynamic type of i. If i is a nil interface value, TypeOf returns nil.
#TypeOf返回表示i的动态类型的反射Type。如果i是nil接口值,则TypeOf返回nil。
func TypeOf(i interface{}) Type
ValueOf
# ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value
# ValueOf返回一个新的Value,初始化为存储在接口i中的具体值。 ValueOf(nil)返回零值
func ValueOf(i interface{}) Value
然后我们可以使用reflect.ValueOf和reflect.TypeOf将接口类型变量分别转换为反射类型:
var p int = 10
v1 := reflect.ValueOf(p)//返回Value类型对象,值为10
t1 := reflect.TypeOf(p)//返回Type类型对象,值为int
fmt.Println("v1:",v1)
fmt.Println("t1",t1)
v2 := reflect.ValueOf(&p)//返回Value类型对象,值为&p,变量p的地址
t2 := reflect.TypeOf(&p)//返回Type类型对象,值为*int
fmt.Println("v2:",v2)
fmt.Println("t2:",t2)
运行结果:
v1: 10
t1: int
v2: 0xc4200180b8
t2: *int
其中v1和v2中包含了接口中的实际值,t1和t2中包含了接口中的实际类型.由于reflect.ValueOf和reflect.TypeOf的参数类型都是interface{},空接口类型,而返回值的类型是reflect.Value和reflect.Type,中间的转换由reflect包来实现。所以就实现了接口类型变量到反射类型对象的转换.
- 反射定律二:反射可以将“反射类型对象”转换为“接口类型变量”,
这里根据一个 reflect.Value类型的变量,我们可以使用Interface方法恢复其接口类型的值。事实上,这个方法会把 type和value信息打包并填充到一个接口变量中,然后返回.
下面我们来看看在reflect包中Value的定义:
func (v Value) Interface() (i interface{})
# Interface returns v's current value as an interface{}. It is equivalent to:
# 接口将v的当前值作为接口{}返回。它相当于:
var i interface{} = (v's underlying value)
# It panics if the Value was obtained by accessing unexported struct fields.
# 如果通过访问未导出的struct字段获得Value,则会发生混乱。
然后我们可以使用Value将反射类型转换为接口类型变量:
var a int = 10
v1 := reflect.ValueOf(&a) //返回Value类型对象,值为&a,变量a的地址
t1 := reflect.TypeOf(&a) //返回Type类型对象,值为*int
fmt.Println("v1:",v1)
fmt.Println("t1:",t1)
v2 := v1.Interface() //返回空接口变量
v3 := v2.(*int) //类型断言,断定v1中type=*int
fmt.Printf("%T %v\n", v3, v3)
fmt.Println("v3:",*v3)
运行的结果:
v1: 0xc420082010
t1: *int
*int 0xc420082010
v3: 10
reflect.ValueOf 的逆操作是 reflect.Value.Interface方法.它返回一个 interface{}类型,装载着与reflect.Value相同的具体值,这样我们就可以将“反射类型对象”转换为“接口类型变量.
其实reflect.Value 和 interface{} 都能装载任意的值. 有所不同的是, 一个空的接口隐藏了值内部的表示方式和所有方法, 因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那样), 内部值我们没法访问. 相比之下, 一个 Value 则有很多方法来检查其内容, 无论它的具体类型是什么.
- 反射定律三:如果要修改反射类型对象,其值必须是“addressable”
在上面第一种反射定律将“接口类型变量”转换为“反射类型对象”我们可以知道,反射对象包含了接口变量中存储的值以及类型。如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值,如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),那么该反射对象是不可以修改的。
那么我们可以通过CanSet函数可以判定反射对象是否可以修改。
# CanSet reports whether the value of v can be changed. A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields. If CanSet returns false, calling Set or any type-specific setter (e.g., SetBool, SetInt) will panic.
# CanSet报告是否可以更改v的值.仅当值可寻址且未通过使用未导出的struct字段获取时,才能更改值。如果CanSet返回false,则调用Set或任何特定于类型的setter(例如,SetBool,SetInt)将会发生混乱。
func (v Value) CanSet() bool
注意:CanSet返回false,值是不可以修改的,如果返回true则是可以修改的.
var p float64 = 3.4
v1 := reflect.ValueOf(&p)
if v1.Kind() == reflect.Ptr && !v1.Elem().CanSet() { //判断是否为指针类型,元素是否可以修改
fmt.Println("cannot set value")
return
} else {
v1 = v1.Elem() //实际取得的对象
fmt.Println("CanSet return bool:", v1.CanSet())
}
v1.SetFloat(6.1)
fmt.Println("p=",p)
运行结果:
CanSet return bool: true
p= 6.1
从运行结果看值修改成功了,但是这里出现了Elem函数作用是用来获取原始值对应的反射对象.
# Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
# Elem返回接口v包含的值或指针v指向的值。如果v的Kind不是Interface或Ptr,它会发生恐慌。如果v为nil,则返回零值。
func (v Value) Elem() Value
在这里要修改变量p的值,首先就要通过reflect.ValueOf来获取p的值类型,refelct.ValueOf返回的值类型是变量p一份值拷贝,要修改变量p就要传递p的地址,从而返回p的地址对象,才可以进行对p变量值对修改操作。在得到p变量的地址值类型之后先调用Elem()返回地址指针指向的值的Value封装。然后通过Set方法进行修改赋值。
通过反射可以很容易的修改变量的值,我们首先要通过反射拿到这个字段的地址值类型,然后去判断反射返回类型是否为reflect.Ptr指针类型(通过指针才能操作对象地址中的值)同时还要判断这个元素是否可以修改,通过Kind()和Set来修改字段的值,然后就可以拿到我们修改的值了.
因此在反射中使用反射包提供refelct.TypeOf和refelct.ValueOf方法获得接口对象的类型,值和方法等。通过反射修改字段值等时候需要传入地址类型,并且需要检查反射返回值类型是否为refelct.Ptr,检查字段是否CanSet,检查字段是存在,然后通过Kind()来帮助赋值相应对类型值。
应用示例:
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name"`
Gender string `json:"gender"`
Age int `json:"age"`
}
func main() {
types := reflect.TypeOf(&User{}).Elem()
value := reflect.ValueOf(&User{}).Elem()
fmt.Println("values Numfield:",value.NumField())
for i:=0;i<types.NumField();i++{
m := types.Field(i).Tag.Get("json")
fmt.Println(m)
}
}
运行:
values Numfield: 3
name
gender
age
通常我们用反射修改一个变量的值:
func main() {
i := 1
v := reflect.ValueOf(&i)
v.Elem().SetInt(10)
fmt.Println(i)
}
> go run reflect.go
10
这里:
- 调用 reflect.ValueOf 函数获取变量指针;
- 调用 reflect.Value.Elem 方法获取指针指向的变量;
- 调用 reflect.Value.SetInt 方法更新变量的值:
由于 Go 语言的函数调用都是值传递的,所以我们只能先获取指针对应的 reflect.Value,再通过 reflect.Value.Elem 方法迂回的方式得到可以被设置的变量,我们通过如下所示的代码理解这个过程:
func main() {
i := 1
v := &i
*v = 10
}
如果不能直接操作 i 变量修改其持有的值,我们就只能获取 i 变量所在地址并使用 *v
修改所在地址中存储的整数。
反射的性能测试
Golang提供了一个testing包,使得单元测试、性能测试尤为简单。只要新建一个以_test结尾的文件,然后使用命令go test就可以自动执行文件中的相应测试函数了(单元测试函数以Test开头,性能测试函数以Benchmark开头)。我们可以使用golang testing来做一下reflect的最简单的性能测试。
Type:Type类型用来表示一个go类型。
不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。
reflect.Type中方法:
// 通用方法
func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。
func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。
func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。
func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。
func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。
func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。
func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。
func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。
func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。
func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。
func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。
func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。
func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。
func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
// 注意对于:数组、切片、映射、通道、指针、接口
func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型
// 数值
func (t *rtype) Bits() int // 获取数值类型的位宽,t 必须是整型、浮点型、复数型
// 数组
func (t *rtype) Len() int // 获取数组的元素个数
// 映射
func (t *rtype) Key() reflect.Type // 获取映射的键类型
// 通道
func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向
// 结构体
func (t *rtype) NumField() int // 获取字段数量
func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段
func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段
func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段
func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段
// 函数
func (t *rtype) NumIn() int // 获取函数的参数数量
func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息
func (t *rtype) NumOut() int // 获取函数的返回值数量
func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息
func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数。
// 如果有可变参数,则 t.In(t.NumIn()-1) 将返回一个切片。
reflect.Value方法:
reflect.Value.Kind():获取变量类别,返回常量.
用于获取值方法:
func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。
func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。
func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。
func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。
func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。
func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。
func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。
func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。
func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。
func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))
func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
// 如果 v 值是未初始化的映射,则返回空列表。
func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。
func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。
func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。
func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。
设置值方法:
func (v Value) SetInt(x int64) //设置int类型的值
func (v Value) SetUint(x uint64) // 设置无符号整型的值
func (v Value) SetFloat(x float64) // 设置浮点类型的值
func (v Value) SetComplex(x complex128) //设置复数类型的值
func (v Value) SetBool(x bool) //设置布尔类型的值
func (v Value) SetString(x string) //设置字符串类型的值
func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。
func (v Value) SetCap(n int) //设置切片的容量
func (v Value) SetBytes(x []byte) //设置字节类型的值
func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在添加
其他的方法:
// 结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量
func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段
func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段
func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)
func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回零值(reflect.ValueOf(nil))
// 通道相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。
func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。
func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。
func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。
func (v Value) Close() // 关闭通道
// 函数相关
func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。
func (v Value) CallSlice(in []Value) []Value // 调用变参函数
有疑问加站长微信联系(非本文作者)