Go是强类型/静态类型语言,每个变量在编译时就已经确定是哪种静态类型。反射(reflection
)是程序在运行时可以访问、检测、修改自身状态或行为的一种能力。在Java
出现后迅速流行起来的概念,Go也提供了这种在运行时更新、检查变量值、调用变量的方法和变量支持的内在操作的机制,一定程度上弥补了静态语言在动态行为上的不足。
反射是把双刃剑,虽然代码更加灵活了但是
- 代码阅读起来也困难了
- 一定程度上破坏了静态类型语言的编译期检查 运行时会有panic风险
- 降低了系统性能
我们为什么需要反射?
- 无法预定义参数类型
- 函数需要根据入参来动态执行
需要注意的是:Go中只有接口类型才可以反射,而反射又是建立在类型系统之上,so我们先来复习下类型与接口的知识
类型
type MyInt int
var i int
var j MyInt
上面的栗子中,i与j具有不同的静态类型(i是int类型,j为MyInt类型),尽管他们的基础类型都是int,但是他们之间不经过转换无法相互赋值。
类型的一个重要类别是接口类型,接口可以存储任何非接口的具体值,只要该值实现了接口方法即可。
接口
接口是多个方法声明的集合,侧重于做什么,不关系怎么做 谁来做。它更像是一种调用契约或协议(protocol)。接口解除了类型依赖,屏蔽了方法实现细节,但接口的实现机制存在运行时开销。
Go
的接口机制比较简洁,不像Java
需要显示声明实现的接口,Go
只要目标类型方法集中包含了接口声明的全部方法,就被称为实现了该接口,无须显示声明。
如果一个接口没有声明任何方法,那么就是一个空接口interface{}
,类似Java
的Object
对象可以被赋值为任意类型的对象。但
Go语言的接口类型不是任意类型 只是任意类型可以通过类型转换成接口变量
接下来我们来看看接口的数据结构,总结起来接口结构如下:
具体可以细分为
- 不包含任何方法的接口
interface{}
- 包含一组方法的接口
Go语言使用runtime.eface
表示不包含任何方法的接口,runtime.iface
表示包含一组方法的接口。
- 不包含任何方法的接口
type eface struct {
_type *_type
data unsafe.Pointer
}
- 包含一组方法的接口
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.
}
可以看到不论空eface
还是非空iface
都包含了_type
数据类型
type _type struct {
size uintptr //类型大小
ptrdata uintptr // 含有所有指针类型前缀大小
hash uint32 //类型hash值 避免在哈希表中计算
tflag tflag //额外类型信息标志
align uint8 // 类型变量对齐方式
fieldalign uint8 // 类型结构字段对齐方式
kind uint8 // 类型种类
alg *typeAlg //存储hash和equal两个操作 map的key就是适用key的_type.alg.hash(k)获取的hash值
// 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
}
当然不同类型需要的描述是不一样的,大多是利用_type
组合其他基础类型而成
接下来我们通过一个栗子拆解下接口内存中的结构究竟如何
type Animal interface {
Say()
}
type Dog struct {
}
func (d *Dog) Say() {
fmt.Println("wang wang")
}
//1
var animal Animal
dog := &Dog{}
//2
animal=dog
//3
var e interface{}
e = dog
- 初始化定义一个接口变量
var animal Animal
- 将实现接口的对象赋值给接口变量
animal=dog
- 定义一个空接口变量
var e interface{}
- 将实现接口的对象赋值给空接口变量
e = dog
至此,想必你应该了解了接口的数据结构及工作机制,接下来我们看看反射是如何工作的
反射
反射三大定律
1. Reflection goes from interface value to reflection object 接口数据-->反射对象
简单来说,反射是一种检查存储在接口变量中的类型和值的机制,reflect
包定义了这两个重要的类型Type
和Value
,任意接口值在反射中都可以理解为由 reflect.Type
和 reflect.Value
两部分组成,可以通过reflect.TypeOf()
和reflect.ValueOf()
函数来获取任意对象的Type
和Value
。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
举个栗子
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
你可能会迷惑,你不是说**接口变量才支持反射的吗?**别着急 我们来仔细看看reflect.TypeOf
和reflect.ValueOf
是如何实现的
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
很简单,当我们调用reflect.TypeOf(x)
时,x
已经存储进了一个空接口变量,reflect.TypeOf
然后拆箱空接口变量获取类型信息。
reflect.TypeOf
和reflect.ValueOf
提供了大量的方法可以让我们检查和操作它们。
type Type interface {
// 从内存中申请一个类型值时对齐的字节数.
Align() int
// 此类型作为结构体字段时对齐的字节数
FieldAlign() int
//获取类型的指定函数信息
Method(int) Method
//通过方法名获取方法信息
MethodByName(string) (Method, bool)
//该类型可导出方法数量
NumMethod() int
// 返回包中定义类型的名称 为定义类型返回空字符串
Name() string
// 返回类型的包路径即唯一标识包的路径 如“encoding/base64”
// 预定义类型、未定义类型、[]int返回空字符串
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
// 方法仅适用于某些类型
// 取决于具体类型
// Int*, Uint*, Float*, Complex*: Bits
// Array: Elem, Len
// Chan: ChanDir, Elem
// Func: In, NumIn, Out, NumOut, IsVariadic.
// Map: Key, Elem
// Ptr: Elem
// Slice: Elem
// Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField
// 返回类型占用的bit值
//非 sized or unsized Int, Uint, Float, or Complex 会panic
Bits() int
// 返回channel类型 非chan类型panic
ChanDir() ChanDir
// 判断函数是否有可变参数 非函数类型会panic
IsVariadic() bool
// 返回元素类型
// 非 Array, Chan, Map, Ptr, or Slice会panic
Elem() Type
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
// 返回结构体类型第i个字段
Field(i int) StructField
// 等价于Field(i)
// It panics if the type's Kind is not Struct.
FieldByIndex(index []int) StructField
// 根据名字返回字段信息
// and a boolean indicating if the field was found.
FieldByName(name string) (StructField, bool)
//利用函数查找字段名符合条件的字段信息 使用广度优先的策略 如果发现多个匹配则不返回匹配项
FieldByNameFunc(match func(string) bool) (StructField, bool)
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
// 返回函数第i个入参
In(i int) Type
// It panics if the type's Kind is not Map.
// 返回map的key类型
Key() Type
// It panics if the type's Kind is not Array.
// 返回数组类型的长度
Len() int
// It panics if the type's Kind is not Struct.
// 返回结构体类型字段数量
NumField() int
// It panics if the type's Kind is not Func.
// 返回函数类型入参数量
NumIn() int
// It panics if the type's Kind is not Func.
// 返回函数类型出参数量
NumOut() int
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumOut()).
// 返回函数类型第i个出参
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
Value
type Value struct {
// typ 包含由Value表示值的类型
typ *rtype
// 指针值数据,如果设置flagIndir则指向数据
// 当设置flagIndir或typ.pointers()为true时有效
ptr unsafe.Pointer
// flag保存有关值的元数据
// 最低位是flag标志位:
// - flagStickyRO: 通过未导出未嵌入的字段获取 故只读
// - flagEmbedRO: 通过未导出嵌入字段获取故只读
// - flagIndir: val保存指向数据的指针
// - flagAddr: v.CanAddr 为 true (表示 flagIndir)
// - flagMethod: v 为方法值
// 接下来的5位给出值的类型
// 重复typ.Kind() 方法值除外.
// 剩余23+位给方法值的方法编号
// 如果flag.kind() != Func, 代码可假定未设置flagMethod
// 如果ifaceIndir(typ), 代码可假设设置了flagIndir
flag
}
2. Reflection goes from reflection object to interface value 反射对象 -->接口数据
像物理反射一样,Go的反射也会生成自己的逆。给出一个reflect.Value
我们可以使用Interface()
方法获取接口的值。实际上就是将该类型和值信息打包成接口表示形式并返回。
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
例如:
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
当然reflect.Value
通过Value.Type()
也可以直接获取reflect.Type
3. To modify a reflection object,the value must be settable 若数据可修改 可通过反射对象来修改它
我们先来看个栗子:
var a float64
fmt.Println(a)
va := reflect.ValueOf(a)
va.SetFloat(11)
fmt.Println(a)
输出:
panic: using unaddressable value
为何?看似操作没问题。其实仔细想想,Go是值传递va := reflect.ValueOf(a)
中我们相当于传递了a
的拷贝给了reflect.ValueOf
,因此即使va.SetFloat(11)
修改成功了也无法到达修改a
原始值的目的,故而利用这种Type是否CanSet
来避免这种问题。正确做法
- 首先根据变量地址获取
reflect.Value
即va := reflect.ValueOf(&a)
va.SetFloat(11)
此时依然无法成功 因为此时的va
仍然是一个拷贝值,如若修改需要使用va.Elem()
获取*va
var a float64
fmt.Println(a)
va := reflect.ValueOf(&a)
va.Elem().SetFloat(11)
fmt.Println(a) // 11
反射的应用
反射广泛应用在对象序列化,fmt
相关的函数以及ORM(Object Relational Mapping)等等
例如:JSON序列化
Go内置的Json序列化提供了两个方法
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
序列化和反序列化参数中都有interface{}
类型的变量,所以当我们调用这个函数时需要使用reflect
包中的方法后期参数的reflect.Value
和reflect.Type
,进而调用其get
、set
方法。
序列化
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
......
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}
总结
Go作为静态语言,相对于动态语言,在灵活性上受到某些限制。但是通过reflect
包提供类似动态语言的功能,你可以运行时获取参数的Value
和Type
进而完成一些特定的需求。其转换关系如图
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!