反射
反射
反射是一种在运行时检查语言自身结构的机制,它可以很灵活的去应对一些问题,但同时带来的弊端也很明显,例如性能问题等等。在Go中,反射与interface{}
密切相关,很大程度上,只要有interface{}
出现的地方,就会有反射。Go中的反射API是由标准库reflect
包提供的。
接口
在开始之前先简单的了解一下位于runtime
包下的两个接口。在Go中,接口本质上是结构体,Go在运行时将接口分为了两大类,一类是没有方法集的接口,另一个类则是有方法集的接口。对于含有方法集的接口来说,在运行时由如下的结构体iface
来进行表示
type iface struct {
tab *itab // 包含 数据类型,接口类型,方法集等
data unsafe.Pointer // 指向值的指针
}
而对于没有方法集接口来说,在运行时由eface
结构体来进行表示,如下
type eface struct {
_type *_type // 类型
data unsafe.Pointer // 指向值的指针
}
而这两个结构体在reflect
包下都有与其对应的结构体类型,iface
对应的是nonEmptyInterface
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // 静态接口类型
typ *rtype // 动态具体类型
hash uint32 // 类型哈希
_ [4]byte
fun [100000]unsafe.Pointer // 方法集
}
word unsafe.Pointer // 指向值的指针
}
而eface
对应的是emptyInterface
type emptyInterface struct {
typ *rtype // 动态具体类型
word unsafe.Pointer // 指向指针的值
}
对于这两种类型,官方给出了很明确的定义
nonEmptyInterface
: nonEmptyInterface is the header for an interface value with methodsemptyInterface
:emptyInterface is the header for an interface{} value
上述提到了动态具体类型这一词,原文为dynamic concrete type
,首先Go语言是一个百分之百的静态类型语言,静态这一词是体现在对外表现的抽象的接口类型是不变的,而动态表示是接口底层存储的具体实现的类型是可以变化的。至此,对于接口的简单原理只需要了解到这里就足够满足后续反射的学习。
桥梁
在reflect
包下,分别有reflect.Type
接口类型来表示Go中的类型,reflect.Value
结构体类型来表示Go中的值
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
上面的代码省略了很多细节,先只需要了解这两个类型的存在即可。Go中所有反射相关的操作都是基于这两个类型,reflect
包提供了两个函数来将Go中的类型转换为上述的两种类型以便进行反射操作,分别是reflect.TypeOf
函数
func TypeOf(i any) Type
与reflect.ValueOf
函数
func ValueOf(i any) Value
可以看到两个函数的参数类型都是any
,也就是interface{}
的别名。如果想要进行反射操作,就需要先将其类型转换为interface{}
,这也是为什么前面提到了只要有反射就离不开空接口。不严谨的说,空接口就是连接Go类型系统与反射的桥梁,下图很形象的描述了其过程。
提示
下文中为了方便,统一使用别名any
来替代interface{}
核心
在Go中有三个经典的反射定律,结合上面所讲的内容也就非常好懂,分别如下
反射可以将
interface{}
类型变量转换成反射对象反射可以将反射对象还原成
interface{}
类型变量要修改反射对象,其值必须是可设置的
这三个定律便是Go反射的核心,当需要访问类型相关信息时,便需要用到reflect.TypeOf
,当需要修改反射值时,就需要用到reflect.ValueOf
类型
reflect.Type
代表着Go中的类型,使用reflect.TypeOf()
函数可以将变量转换成reflect.Type
。代码示例如下
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}
输出结果为
string
Kind
对于Type
而言,Go内部使用reflect.Kind
来表示Go中的基础类型,其本质上是无符号整型uint
。
type Kind uint
reflect
包使用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
Pointer
Slice
String
Struct
UnsafePointer
)
Kind
类型仅仅实现了Stringer
接口的String()
方法,该类型也仅有这一个方法,String()
方法的返回值来自于一个其内部的map
,如下所示
var kindNames = []string{
Invalid: "invalid",
Bool: "bool",
Int: "int",
Int8: "int8",
Int16: "int16",
Int32: "int32",
Int64: "int64",
Uint: "uint",
Uint8: "uint8",
Uint16: "uint16",
Uint32: "uint32",
Uint64: "uint64",
Uintptr: "uintptr",
Float32: "float32",
Float64: "float64",
Complex64: "complex64",
Complex128: "complex128",
Array: "array",
Chan: "chan",
Func: "func",
Interface: "interface",
Map: "map",
Pointer: "ptr",
Slice: "slice",
String: "string",
Struct: "struct",
UnsafePointer: "unsafe.Pointer",
}
type Type interface{
Kind() Kind
}
通过Kind
,可以知晓空接口存储的值究竟是什么基础类型,例如
func main() {
// 声明一个any类型的变量
var eface any
// 赋值
eface = 100
// 通过Kind方法,来获取其类型
fmt.Println(reflect.TypeOf(eface).Kind())
}
输出结果
int
Elem
type Type interface{
Elem() Type
}
使用Type.Elem()
方法,可以判断类型为any
的数据结构所存储的元素类型,可接收的底层参数类型必须是指针,切片,数组,通道,映射表其中之一,否则会panic
。下面是代码示例
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// key()会返回map的键反射类型
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}
输出为
string
int
指针也可以理解为是一个容器,对于指针使用Elem()
会获得其指向元素的反射类型,代码示例如下
func main() {
var eface any
// 赋值指针
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// 拿到指针所指向元素的反射类型
vType := rType.Elem()
// 输出包路径
fmt.Println(vType.PkgPath())
// 输出其名称
fmt.Println(vType.Name())
}
strings
Builder
对于数组,切片,通道用使用起来都是类似的。
Size
type Type interface{
Size() uintptr
}
通过Size
方法可以获取对应类型所占的字节大小,示例如下
func main() {
fmt.Println(reflect.TypeOf(0).Size())
fmt.Println(reflect.TypeOf("").Size())
fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
fmt.Println(reflect.TypeOf(0.1).Size())
fmt.Println(reflect.TypeOf([]string{}).Size())
}
输出结果为
8
16
16
8
24
提示
使用unsafe.Sizeof()
可以达到同样的效果
Comparable
type Type interface{
Comparable() bool
}
通过Comparable
方法可以判断一个类型是否可以被比较,例子如下
func main() {
fmt.Println(reflect.TypeOf("hello world!").Comparable())
fmt.Println(reflect.TypeOf(1024).Comparable())
fmt.Println(reflect.TypeOf([]int{}).Comparable())
fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}
输出如下
true
true
false
true
Implements
type Type interface{
Implements(u Type) bool
}
通过Implements
方法可以判断一个类型是否实现了某一接口
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}
输出结果
true
false
ConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}
使用ConvertibleTo
方法可以判断一个类型是否可以被转换为另一个指定的类型
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}
输出
true
false
值
reflect.Value
代表着反射接口的值,使用reflect.ValueOf()
函数可以将变量转换成reflect.Value
。代码示例如下
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}
输出结果为
hello world!
Type
func (v Value) Type() Type
Type
方法可以获取一个反射值的类型
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}
输出
int
Elem
func (v Value) Elem() Value
获取一个反射值的元素反射值
func main() {
num := new(int)
*num = 114514
// 以指针为例子
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}
输出
114514
指针
获取一个反射值的指针方式有两种
// 返回一个表示v地址的指针反射值
func (v Value) Addr() Value
// 返回一个指向v的原始值的uinptr 等价于 uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr
// 返回一个指向v的原始值的uintptr
// 仅当v的Kind为 Chan, Func, Map, Pointer, Slice, UnsafePointer时,否则会panic
func (v Value) Pointer() uintptr
// 返回一个指向v的原始值的unsafe.Pointer
// 仅当v的Kind为 Chan, Func, Map, Pointer, Slice, UnsafePointer时,否则会panic
func (v Value) UnsafePointer() unsafe.Pointer
示例如下
func main() {
num := 1024
ele := reflect.ValueOf(&num).Elem()
fmt.Println("&num", &num)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}
输出
&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058
提示
fmt.Println
会反射获取参数的类型,如果是reflect.Value
类型的话,会自动调用Value.Interface()
来获取其原始值。
换成一个map再来一遍
func main() {
dic := map[string]int{}
ele := reflect.ValueOf(&dic).Elem()
println(dic)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
fmt.Println("UnsafePointer", ele.UnsafePointer())
}
输出
0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0
设置值
func (v Value) Set(x Value)
倘若通过反射来修改反射值,那么其值必须是可取址的,这时应该通过指针来修改其元素值,而不是直接尝试修改元素的值。
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// 获取指针指向的元素
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}
输出如下
114514
11
获取值
func (v Value) Interface() (i any)
通过Interface()
方法可以获取反射值原有的值
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}
输出
hello
函数
通过反射可以获取函数的一切信息,也可以反射调用函数
信息
通过反射类型来获取函数的一切信息
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// 输出函数名称,字面量函数的类型没有名称
fmt.Println(rType.Name())
// 输出参数,返回值的数量
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// 输出第一个参数的类型
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// 输出第一个返回值的类型
fmt.Println(rResType.Kind())
}
输出
2 1
int
int
调用
通过反射值来调用函数
func (v Value) Call(in []Value) []Value
func main() {
// 获取函数的反射值
rType := reflect.ValueOf(Max)
// 传入参数数组
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}
输出
50
结构体
假设有如下结构体
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}
访问字段
reflect.StructField
结构的结构如下
type StructField struct {
// 字段名称
Name string
// 包名
PkgPath string
// 类型名
Type Type
// Tag
Tag StructTag
// 字段的字节偏移
Offset uintptr
// 索引
Index []int
// 是否为嵌套字段
Anonymous bool
}
访问结构体字段的方法有两种,一种是通过索引来进行访问,另一种是通过名称。
type Type interface{
Field(i int) StructField
}
通过索引访问的例子如下
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// 输出结构体字段的数量
fmt.Println(rType.NumField())
for i := 0; i < rType.NumField(); i++ {
structField := rType.Field(i)
fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
}
}
输出
4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 false
type Type interface{
FieldByName(name string) (StructField, bool)
}
通过名称访问的例子如下
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// 输出结构体字段的数量
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}
输出
4
money int false
修改字段
倘若要修改结构体字段值,则必须传入一个结构体指针,下面是一个修改字段的例子
func main() {
// 传入指针
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// 获取字段
name := rValue.FieldByName("Name")
// 修改字段值
if (name != reflect.Value{}) { // 如果返回reflect.Value{},则说明该字段不存在
name.SetString("jack")
}
// 输出结构体
fmt.Println(rValue.Interface())
}
输出
{jack 0 0}
对于修改结构体私有字段而言,需要进行一些额外的操作,如下
func main() {
// 传入指针
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// 获取一个私有字段
money := rValue.FieldByName("money")
// 修改字段值
if (money != reflect.Value{}) {
// 构造指向该结构体未导出字段的指针反射值
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// 获取该指针所指向的元素,也就是要修改的字段
field := p.Elem()
// 修改值
field.SetInt(164)
}
// 输出结构体
fmt.Printf("%+v\n", rValue.Interface())
}
访问Tag
获取到StructField
后,便可以直接访问其Tag
// 如果不存在,ok为false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// 如果不存在,返回空字符串
func (tag StructTag) Get(key string) string
示例如下
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
name, ok := rType.FieldByName("Name")
if ok {
fmt.Println(name.Tag.Lookup("json"))
fmt.Println(name.Tag.Get("json"))
}
}
输出
name true
name
访问方法
访问方法与访问字段的过程很相似,只是函数签名略有区别。reflect.Method
结构体如下
type Method struct {
// 方法名
Name string
// 包名
PkgPath string
// 方法类型
Type Type
// 方法对应的函数,第一个参数是接收者
Func Value
// 索引
Index int
}
访问方法信息示例如下
func main() {
// 获取结构体反射类型
rType := reflect.TypeOf(new(Person)).Elem()
// 输出方法个数
fmt.Println(rType.NumMethod())
// 遍历输出方法信息
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
}
}
输出
1
0 Talk func(main.Person, string) string true
如果想要获取方法的参数和返回值细节,可以通过Method.Func
来进行获取,过程与访问函数信息一致,将上面的代码稍微修改下
func main() {
// 获取结构体反射类型
rType := reflect.TypeOf(new(Person)).Elem()
// 输出方法个数
fmt.Println(rType.NumMethod())
// 遍历输出方法信息
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("方法参数")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("方法返回值")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}
可以看到第一个参数是main.Person
,也就是接收者类型
1
0 Talk func(main.Person, string) string true
方法参数
main.Person
string
方法返回值
string
调用方法
调用方法与调用函数的过程相似,而且并不需要手动传入接收者,例子如下
func main() {
// 获取结构体反射类型
rValue := reflect.ValueOf(new(Person)).Elem()
// 输出方法个数
fmt.Println(rValue.NumMethod())
// 遍历输出方法信息
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// 调用方法,并获取返回值
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// 遍历输出返回值
for _, re := range res {
fmt.Println(re.Interface())
}
}
}
输出
1
hello,reflect!
创建
通过反射可以构造新的值,reflect
包同时根据一些特殊的类型提供了不同的更为方便的函数。
基本类型
// 返回指向反射值的指针反射值
func New(typ Type) Value
以string
为例
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}
hello world!
结构体
结构体的创建同样用到reflect.New
函数
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}
func main() {
// 创建结构体反射值
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}
输出
{ 0 0}
切片
反射创建切片
func MakeSlice(typ Type, len, cap int) Value
func main() {
// 创建切片反射值
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// 遍历赋值
for i := 0; i < 10; i++ {
rValue.Index(i).SetInt(int64(i))
}
fmt.Println(rValue.Interface())
}
[0 1 2 3 4 5 6 7 8 9]
Map
反射创建Map
func MakeMapWithSize(typ Type, n int) Value
func main() {
//构建map反射值
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// 设置值
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}
map[a:1]
管道
反射创建管道
func MakeChan(typ Type, buffer int) Value
func main() {
// 创建管道反射值
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}
函数
反射创建函数
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
func main() {
// 传入包装类型和函数体
fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
for _, arg := range args {
fmt.Println(arg.Interface())
}
return nil
})
fmt.Println(fn.Type())
fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}
输出
func(int)
1024
深度相等
reflect.DeepEqual
是反射包下提供的一个用于判断两个变量是否深度相等的函数,签名如下。
func DeepEqual(x, y any) bool
该函数对于每一种基础类型都做了处理,下面是一些类型判断方式。
- 数组:数组中的每一个元素都深度相等
- 切片:都为
nil
时,判为深度相等,或者都不为空时,长度范围内的元素深度相等 - 结构体:所有字段都深度相等
- 映射表:都为
nil
时,为深度相等,都不为nil
时,每一个键所映射的值都深度相等 - 指针:指向同一个元素或指向的元素深度相等
- 接口:接口的具体类型深度相等时
- 函数:只有两者都为
nil
时才是深度相等,否则就不是深度相等
下面是一些例子:
切片
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}
输出
true
结构体
func main() {
mike := Person{
Name: "mike",
Age: 39,
Father: nil,
}
jack := Person{
Name: "jack",
Age: 18,
Father: &mike,
}
tom := Person{
Name: "tom",
Age: 18,
Father: &mike,
}
fmt.Println(reflect.DeepEqual(mike, jack))
fmt.Println(reflect.DeepEqual(tom, jack))
fmt.Println(reflect.DeepEqual(jack, jack))
}
输出
false
false
true