反射是指计算机程序在运行时(Run time)能够访问、检测和修改它自己状态或行为的一种能力。用比喻来讲,反射就是程序在运行的时候可以“观察”而且修改本身的行为。利用 Go 语言的反射机制,能够获取结构体的公有字段以及私有字段的标签名,甚至一些其余比较敏感的信息。html
1.有时编写一个函数,可是并不知道传给你的参数类型是什么,多是没约定好;也多是传入的类型不少,这些类型并不能统一表示。这时反射就会用的上了。程序员
2.有时候须要根据某些条件决定调用哪一个函数,好比根据用户的输入来决定。这时就须要对函数和函数的参数进行反射,在运行期间动态地执行函数。golang
interface是Go 语言实现抽象的一个很是强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是经过接口的类型信息实现的,反射创建在类型的基础上。数组
Go 语言在 reflect 包里定义了各类类型,实现了反射的各类函数,经过它们能够在运行时检测类型的信息、改变类型的值。markdown
types和interface Go 语言中,每一个变量都有一个静态类型,在编译阶段就肯定了的,好比 int, float64, []int 等等。注意,这个类型是声明时候的类型,不是底层数据类型.例如:函数
type MyInt int
var i int
var j MyInt
复制代码
尽管 i,j 的底层类型都是 int,但咱们知道,他们是不一样的静态类型,除非进行类型转换,不然,i 和 j 不能同时出如今等号两侧。j 的静态类型就是 MyInt工具
反射主要与 interface{} 类型相关 interface底层有两种结构:带接口的和不带接口的oop
带接口的interface底层结构以下:ui
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32
bad bool
inhash bool
unused [2]byte
fun [1]uintptr
}
复制代码
itab:由具体类型 _type 以及 interfacetype 组成。_type 表示具体类型,而 interfacetype 则表示具体类型实现的接口类型, 也就是当前接口的类型。fun是一个接口列表,表示的是_type所表明的具体类型所实现的接口。spa
interfacetype表示的是当前接口变量的静态类型,而itab._type表示的是当前接口变量的动态类型。当前接口变量可以调用的接口取决于它的静态类型
不带接口的interface底层结构以下:
type eface struct {
_type *_type
data unsafe.Pointer
}
复制代码
eface 比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值
使用简单示例说明interface使用方法
首先定义两个接口:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
复制代码
接下来定义一段代码:
var r io.Reader
tty, err := os.OpenFile("/Users/qcrao/Desktop/test", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
复制代码
首先声明 r 的类型是 io.Reader,注意,这是 r 的静态类型,此时它的动态类型为 nil,而且它的动态值也是 nil。
以后,r = tty 这一语句,将 r 的动态类型变成 *os.File,动态值则变成非空,表示打开的文件对象。这时,r 能够用<value, type>对来表示为: <tty, *os.File>。
此时虽然 fun 所指向的函数只有一个 Read 函数,其实 *os.File 还包含 Write 函数,也就是说 *os.File 其实还实现了 io.Writer 接口。所以下面的断言语句能够执行:
var w io.Writer
w = r.(io.Writer)
复制代码
之因此用断言,而不能直接赋值,是由于 r 的静态类型是 io.Reader,并无实现 io.Writer 接口。断言可否成功,看 r 的动态类型是否符合要求。其实r的真实类型*os.File实现了Write接口,所以是符合要求的。
这样,w 也能够表示成 <tty, *os.File>,仅管它和 r 同样,可是 w 可调用的函数取决于它的静态类型 io.Writer,也就是说它只能有这样的调用形式: w.Write() 。
和 r 相比,仅仅是 fun 对应的函数变了:Read -> Write。
最后,再来一个赋值:
var empty interface{}
empty = w
复制代码
因为 empty 是一个空接口,所以全部的类型都实现了它,w 能够直接赋给它,不须要执行断言操做。
reflect 包里定义了一个接口和一个结构体,即 reflect.Type 和 reflect.Value,它们提供不少函数来获取存储在接口里的类型信息。
reflect.Type 主要提供关于类型相关的信息,因此它和 _type 关联比较紧密;reflect.Value 则结合 _type 和 data 二者,所以程序员能够获取甚至改变类型的值。
reflect 包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
复制代码
TypeOf函数:用来提取一个接口中值的类型信息。因为它的输入参数是一个空的interface,调用此函数时,实参会先被转化为 interface{}类型。这样,实参的类型信息、方法集、值信息都存储到 interface{} 变量里了. TypeOf源码以下:
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
复制代码
其中emptyInterface以下:
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
复制代码
toType函数只是作了一个类型转换:
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
复制代码
返回值 Type 其实是一个接口,定义了不少方法,用来获取类型相关的各类信息,而 *rtype 实现了 Type 接口:
type Type interface {
// 全部的类型均可以调用下面这些函数
// 此类型的变量对齐后所占用的字节数
Align() int
// 若是是 struct 的字段,对齐后占用的字节数
FieldAlign() int
// 返回类型方法集里的第 `i` (传入的参数)个方法
Method(int) Method
// 经过名称获取方法
MethodByName(string) (Method, bool)
// 获取类型方法集里导出的方法个数
NumMethod() int
// 类型名称
Name() string
// 返回类型所在的路径,如:encoding/base64
PkgPath() string
// 返回类型的大小,和 unsafe.Sizeof 功能相似
Size() uintptr
// 返回类型的字符串表示形式
String() string
// 返回类型的类型值
Kind() Kind
// 类型是否实现了接口 u
Implements(u Type) bool
// 是否能够赋值给 u
AssignableTo(u Type) bool
// 是否能够类型转换成 u
ConvertibleTo(u Type) bool
// 类型是否能够比较
Comparable() bool
// 下面这些函数只有特定类型能够调用
// 如:Key, Elem 两个方法就只能是 Map 类型才能调用
// 类型所占据的位数
Bits() int
// 返回通道的方向,只能是 chan 类型调用
ChanDir() ChanDir
// 返回类型是不是可变参数,只能是 func 类型调用
// 好比 t 是类型 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool
// 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
Elem() Type
// 返回结构体类型的第 i 个字段,只能是结构体类型调用
// 若是 i 超过了总字段数,就会 panic
Field(i int) StructField
// 返回嵌套的结构体的字段
FieldByIndex(index []int) StructField
// 经过字段名称获取字段
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// 返回名称符合 func 函数的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 获取函数类型的第 i 个参数的类型
In(i int) Type
// 返回 map 的 key 类型,只能由类型 map 调用
Key() Type
// 返回 Array 的长度,只能由类型 Array 调用
Len() int
// 返回类型字段的数量,只能由类型 Struct 调用
NumField() int
// 返回函数类型的输入参数个数
NumIn() int
// 返回函数类型的返回值个数
NumOut() int
// 返回函数类型的第 i 个值的类型
Out(i int) Type
// 返回类型结构体的相同部分
common() *rtype
// 返回类型结构体的不一样部分
uncommon() *uncommonType
}
复制代码
ttype结构以下:
type rtype struct {
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
alg *typeAlg
gcdata *byte
str nameOff
ptrToThis typeOff
}
复制代码
全部的类型都会包含 rtype 这个字段,表示各类类型的公共信息;另外,不一样类型包含本身的一些独特的部分。正由于全部类型都包含了rtype字段,才会有eface := *(*emptyInterface)(unsafe.Pointer(&i))
取到rtype
字段
好比下面的 arrayType 和 chanType 都包含 rytpe,而前者还包含 slice,len 等和数组相关的信息;后者则包含 dir 表示通道方向的信息。
// arrayType represents a fixed array type.
type arrayType struct {
rtype `reflect:"array"`
elem *rtype // array element type
slice *rtype // slice type
len uintptr
}
// chanType represents a channel type.
type chanType struct {
rtype `reflect:"chan"`
elem *rtype // channel element type
dir uintptr // channel direction (ChanDir)
}
复制代码
讲完了 TypeOf 函数,再来看一下 ValueOf 函数。返回值 reflect.Value 表示 interface{} 里存储的实际变量,它能提供实际变量的各类信息。相关的方法经常是须要结合类型信息和值信息。例如,若是要提取一个结构体的字段信息,那就须要用到 _type (具体到这里是指 structType) 类型持有的关于结构体的字段信息、偏移信息,以及 *data 所指向的内容 —— 结构体的实际值。
源码以下:
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// ……
return unpackEface(i)
}
// 分解 eface
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
复制代码
从源码看,比较简单:将先将 i 转换成 *emptyInterface 类型, 再将它的 typ 字段和 word 字段以及一个标志位字段组装成一个 Value 结构体,而这就是 ValueOf 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。
Value 结构体定义了不少方法,经过这些方法能够直接操做 Value 字段 ptr 所指向的实际数据:
// 设置切片的 len 字段,若是类型不是切片,就会panic
func (v Value) SetLen(n int)
// 设置切片的 cap 字段
func (v Value) SetCap(n int)
// 设置字典的 kv
func (v Value) SetMapIndex(key, val Value)
// 返回切片、字符串、数组的索引 i 处的值
func (v Value) Index(i int) Value
// 根据名称获取结构体的内部字段值
func (v Value) FieldByName(name string) Value
// 用来获取 int 类型的值
func (v Value) Int() int64
// 用来获取结构体字段(成员)数量
func (v Value) NumField() int
// 尝试向通道发送数据(不会阻塞)
func (v Value) TrySend(x reflect.Value) bool
// 经过参数列表 in 调用 v 值所表明的函数(或方法
func (v Value) Call(in []Value) (r []Value)
// 调用变参长度可变的函数
func (v Value) CallSlice(in []Value) []Value
复制代码
另外,经过 Type() 方法和 Interface() 方法能够打通 interface、Type、Value 三者。Type() 方法也能够返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface() 方法能够将 Value 还原成原来的 interface。
总结一下:TypeOf() 函数返回一个接口,这个接口定义了一系列方法,利用这些方法能够获取关于类型的全部信息; ValueOf() 函数返回一个结构体变量,包含类型信息以及实际值。
用一张图来串一下:
上图中,rtye 实现了 Type 接口,是全部类型的公共部分。emptyface 结构体和 eface 实际上是一个东西,而 rtype 其实和 _type 是一个东西,只是一些字段稍微有点差异,好比 emptyface 的 word 字段和 eface 的 data 字段名称不一样,可是数据型是同样的。
反射其实主要是对接口的处理,所以必定要对接口的底层结构很是熟悉。
第一条:反射是一种检测存储在 interface 中的类型和值机制。这能够经过 TypeOf 函数和 ValueOf 函数获得。
第二条:将 ValueOf 的返回值经过 Interface() 函数反向转变成 interface 变量。
第三条:若是须要操做一个反射变量,那么它必须是可设置的。
复制代码
前两条就是说 接口型变量 和 反射类型对象 能够相互转化,反射类型对象实际上就是指的前面说的 reflect.Type 和 reflect.Value。
第三条不太好懂:若是须要操做一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量自己,这样对反射变量的操做,就会反映到原变量自己;反之,若是反射变量不能表明原变量,那么操做了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。因此第二种状况在语言层面是不被容许的。
举个例子:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
复制代码
执行上面的代码会产生 panic,缘由是反射变量 v 不能表明 x 自己,为何?由于调用 reflect.ValueOf(x) 这一行代码的时候,传入的参数在函数内部只是一个拷贝,是值传递,因此 v 表明的只是 x 的一个拷贝,所以对 v 进行操做是被禁止的。
可设置是反射变量 Value 的一个性质,但不是全部的 Value 都是可被设置的。
就像在通常的函数里那样,当咱们想改变传入的变量时,使用指针就能够解决了
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
结果:
type of p: *float64
settability of p: false
复制代码
p 还不是表明 x,p.Elem() 才真正表明 x,这样就能够真正操做 x 了:
v := p.Elem() // 注意,前面p := reflect.ValueOf(&x)必定要传入指针,不然这里会panic
v.SetFloat(7.1)
fmt.Println(v.Interface()) // 7.1
fmt.Println(x) // 7.1
复制代码
关于第三条,记住一句话:若是想要操做原变量,反射变量 Value 必需要 hold 住原变量的地址才行。
利用反射机制,对于结构体中未导出成员,能够读取,但不能修改其值。
注意,正常状况下,代码是不能读取结构体未导出成员的,但经过反射能够越过这层限制。另外,经过反射,结构体中能够被修改的成员只有是导出成员,也就是字段名的首字母是大写的。
一个可取地址的 reflect.Value 变量会记录一个结构体成员是不是未导出成员,若是是的话则拒绝修改操做。
CanAddr 不能说明一个变量是否能够被修改。
CanSet 则能够检查对应的 reflect.Value 是否可取地址并可被修改。
复制代码
【1】深度解密Go语言之反射
【2】Go 反射机制介绍