Go的反射有哪些应用?
- IDE中代码的自动补全
- 对象序列化
- fmt函数的相关实现
- ORM框架
什么情况下需要使用反射?
- 不能明确函数调用哪个接口,需要根据传入的参数在运行时决定。
- 不能明确传入函数的参数类型,需要在运行时处理任意对象。
反射对性能有消耗,而且可读性低,能不用就不要用反射。
如何比较两个对象完全相同?
Go中提供了一个函数可以实现这个功能:
func DeepEqual(x, y interface{}) bool
DeepEqual
函数的参数是两个 interface
,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。
先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。
例如这个代码:
type MyInt int
type YourInt int
func main() {
m := MyInt(1)
y := YourInt(1)
fmt.Println(reflect.DeepEqual(m, y))
}
这个代码的结果是false。
上面的代码中,m, y 底层都是 int,而且值都是 1,但是两者静态类型不同,前者是 MyInt
,后者是 YourInt
,因此两者不是“深度”相等。
来看一下源码:
func DeepEqual(x, y any) bool {
if x == nil || y == nil {
return x == y
}
v1 := ValueOf(x)
v2 := ValueOf(y)
if v1.Type() != v2.Type() {
return false
}
return deepValueEqual(v1, v2, make(map[visit]bool))
}
首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true
接着,使用反射,获取x,y 的反射对象,并且立即比较两者的类型,根据前面的内容,这里实际上是动态类型,如果类型不同,直接返回 false。
最后,最核心的内容在子函数 deepValueEqual
中。
然后我们来看一下deepValueEqual
的源码:
// Tests for deep equality using reflected types. The map argument tracks
// comparisons that have already been seen, which allows short circuiting on
// recursive types.
func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
if v1.Type() != v2.Type() {
return false
}
// We want to avoid putting more in the visited map than we need to.
// For any possible reference cycle that might be encountered,
// hard(v1, v2) needs to return true for at least one of the types in the cycle,
// and it's safe and valid to get Value's internal pointer.
hard := func(v1, v2 Value) bool {
switch v1.Kind() {
case Pointer:
if v1.typ.ptrdata == 0 {
// go:notinheap pointers can't be cyclic.
// At least, all of our current uses of go:notinheap have
// that property. The runtime ones aren't cyclic (and we don't use
// DeepEqual on them anyway), and the cgo-generated ones are
// all empty structs.
return false
}
fallthrough
case Map, Slice, Interface:
// Nil pointers cannot be cyclic. Avoid putting them in the visited map.
return !v1.IsNil() && !v2.IsNil()
}
return false
}
if hard(v1, v2) {
// For a Pointer or Map value, we need to check flagIndir,
// which we do by calling the pointer method.
// For Slice or Interface, flagIndir is always set,
// and using v.ptr suffices.
ptrval := func(v Value) unsafe.Pointer {
switch v.Kind() {
case Pointer, Map:
return v.pointer()
default:
return v.ptr
}
}
addr1 := ptrval(v1)
addr2 := ptrval(v2)
if uintptr(addr1) > uintptr(addr2) {
// Canonicalize order to reduce number of entries in visited.
// Assumes non-moving garbage collector.
addr1, addr2 = addr2, addr1
}
// Short circuit if references are already seen.
typ := v1.Type()
v := visit{addr1, addr2, typ}
if visited[v] {
return true
}
// Remember for later.
visited[v] = true
}
switch v1.Kind() {
case Array:
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
return false
}
}
return true
case Slice:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
// Special case for []byte, which is common.
if v1.Type().Elem().Kind() == Uint8 {
return bytealg.Equal(v1.Bytes(), v2.Bytes())
}
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
return false
}
}
return true
case Interface:
if v1.IsNil() || v2.IsNil() {
return v1.IsNil() == v2.IsNil()
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
case Pointer:
if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
case Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {
return false
}
}
return true
case Map:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
for _, k := range v1.MapKeys() {
val1 := v1.MapIndex(k)
val2 := v2.MapIndex(k)
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {
return false
}
}
return true
case Func:
if v1.IsNil() && v2.IsNil() {
return true
}
// Can't do better than this:
return false
case Int, Int8, Int16, Int32, Int64:
return v1.Int() == v2.Int()
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return v1.Uint() == v2.Uint()
case String:
return v1.String() == v2.String()
case Bool:
return v1.Bool() == v2.Bool()
case Float32, Float64:
return v1.Float() == v2.Float()
case Complex64, Complex128:
return v1.Complex() == v2.Complex()
default:
// Normal equality suffices
return valueInterface(v1, false) == valueInterface(v2, false)
}
}
这个代码的思路很清晰,就是分别递归调用deepValueEqual
函数,一直递归到最进本的数据类型,比较int
, string
等可以直接得出true
或者false
,再一层层的返回,最终得到深度相等的比较结果。
Go语言是如何实现反射的?
interface
,它是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。
Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。
type和interface
我们需要先介绍一下什么叫做静态类型,什么叫做动态类型。
静态类型
所谓的静态类型(即 static type),就是变量声明的时候的类型。
var age int // int 是静态类型
var name string // string 也是静态类型
它是你在编码时,肉眼可见的类型。
动态类型
所谓的 动态类型(即 concrete type,也叫具体类型)是 程序运行时系统才能看见的类型。
这是什么意思呢?
我们都知道 空接口 可以承接什么问题类型的值,什么 int 呀,string 呀,都可以接收。
比如下面这几行代码
var i interface{}
i = 18
i = "Go编程时光"
第一行:我们在给 i
声明了 interface{}
类型,所以 i
的静态类型就是 interface{}
第二行:当我们给变量 i
赋一个 int 类型的值时,它的静态类型还是 interface{},这是不会变的,但是它的动态类型此时变成了 int 类型。
第三行:当我们给变量 i
赋一个 string 类型的值时,它的静态类型还是 interface{},它还是不会变,但是它的动态类型此时又变成了 string 类型。
从以上,可以知道,不管是 i=18
,还是 i="Go编程时光"
,都是当程序运行到这里时,变量的类型,才发生了改变,这就是我们最开始所说的 动态类型是程序运行时系统才能看见的类型。
Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了的,比如 int, float64, []int
等等。注意,这个类型是声明时候的类型,不是底层数据类型。
Go官方的博客里面就举过一个例子:
type MyInt int
var i int
var j MyInt
尽管 i,j 的底层类型都是 int,但我们知道,他们是不同的静态类型,除非进行类型转换,否则,i 和 j 不能同时出现在等号两侧。j 的静态类型就是 MyInt
。
反射跟interface{}
的关系十分密切,因此我们需要先学习一下interface{}
的原理。
interface{}
非空interface{}
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}
itab
主要由具体类型_type
和结构类型interfacetype
组成。
我们可以用一张图来理顺中间的关系:
空interface{}
type eface struct {
_type *_type
data unsafe.Pointer
}
相比 iface
,eface
就比较简单了。只维护了一个 _type
字段,表示空接口所承载的具体的实体类型。data
描述了具体的值。
接口变量可以存储任何实现了接口定义的所有方法的变量。
Go中常见的接口Reader
和Writer
接口:
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("./", 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
的动态类型是否符合要求。
这样,w 也可以表示成 <tty, *os.File>
,仅管它和 r
一样,但是 w 可调用的函数取决于它的静态类型 io.Writer
,也就是说它只能有这样的调用形式: w.Write()
。w
的内存形式如下图:
最后,我们再来一个赋值:
var empty interface{}
empty = w
由于 empty
是一个空接口,因此所有的类型都实现了它,w 可以直接赋给它,不需要执行断言操作。
从上面的三张图可以看到,interface
包含三部分的信息:_type
是类型信息,*data
指向实际类型的实际值,itab
包含实际类型的信息,包括大小,包路径,还包含绑定在类型上的各种方法。
反射的基本函数
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{}
变量里了。
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
这里的 emptyInterface
和上面提到的 eface
是一回事(字段名略有差异,字段是相同的),并且在不同的源码包:前者在 reflect
包,后者在 runtime
包。 eface.typ
就是动态类型。
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 {
// Methods applicable to all types.
// 此类型的变量对齐后占用的字节数
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
// 类型占据的位数
Bits() int
// 返回通道的方向,只能是chan类型调用
ChanDir() ChanDir
// 返回类型是否是可变参数,只能是func类型调用
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)
// 返回名称符合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
}
可见Type
定义了非常多的方法,通过它们可以获取到类型的所有信息。
注意到Type
方法集的倒数第二个方法common
返回的rtype
类型,它和_type
是一回事,而且源代码里面也注释了,两边要保持同步。
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
这个字段,表示各种类型的公共信息;另外,不同类型包含自己的一些独特的部分。
比如下面的 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)
}
注意到,Type
接口实现了 String()
函数,满足 fmt.Stringer
接口,因此使用 fmt.Println
打印的时候,输出的是 String()
的结果。另外,fmt.Printf()
函数,如果使用 %T
来作为格式参数,输出的是 reflect.TypeOf
的结果,也就是动态类型。例如:
fmt.Printf("%T", 3) // int
TypeOf
函数讲完了,我们接下来来看一下ValueOf
函数。返回值reflect.Value
表示interface{}
里面存储的实际变量,它能提供实际变量的各种信息。
源码如下:
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 字段名称不同,但是数据型是一样的。
反射的三大的定律
根据 Go 官方关于反射的博客,反射有三大定律:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
第一条是最基本的:反射是一种检测存储在 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()
v.SetFloat(7.1)
fmt.Println(v.Interface()) // 7.1
fmt.Println(x) // 7.1
关于第三条,记住一句话:如果想要操作原变量,反射变量 Value
必须要 hold 住原变量的地址才行。
参考自:码神桃花源的博客。