反射
Go语言提供了一种机制,在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用类型的方法,以及直接对它们的布局进行操作,这种机制称为反射(reflection)。
一、relect.Type
1、什么是 relect.Type
?
Go
语言中任何变量都包含reflect.Type
与reflect.Value
两部分信息。
reflect.Type
是声明变量时的类型
,它在Go
中被定义成接口。
type Type interface {
Align() int
FieldAlign() int
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
Bits() int
ChanDir() ChanDir
IsVariadic() bool
Elem() Type //取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回nil的Value
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
In(i int) Type
Key() Type
Len() int
NumField() int
NumIn() int
NumOut() int
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
2、怎样区分reflect.Type
(类型)与 reflect.Kind
(种类)?
reflect.Type.Name()
就是声明变量时指定的类型。Go
语言是一门静态类型的语言,每个变量都有一个静态类型,类型在编译的时候确定下来。不管变量的值如何改变,变量的类型永远不变。两个不同的静态类型之间,是不能赋值的(如果没有进行类型转换 )。
type MyInt int
var i int = 5
var j MyInt = 10
j = i // build error: cannot use i (type int) as type MyInt in assignment
reflect.Kind
是Go
中定义的内置类型(底层数据类型), 比如这些:
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
示例:区分Type
与Kind
package main
import (
"fmt"
"reflect"
)
type MyType int
type Stu struct {
name string
score float32
}
func test1(a interface{}) {
fmt.Printf("the type of a is: %v\n", reflect.TypeOf(a))
fmt.Printf("the kind of a is: %v\n\n", reflect.ValueOf(a).Kind())
}
func main() {
var a Stu
var b MyType
test1(a)
test1(b)
}
输出:
the type of a is: main.Stu
the kind of a is: struct
the type of a is: main.MyType
the kind of a is: int
3、指针反射
Go
语言程序中对指针获取反射对象时,可以通过 reflect.Elem()
方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*
操作。
reflect.Elem()
返回的是Type
// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type
Go语言的反射中对所有指针变量的
种类
都是Ptr
,但需要注意的是,指针变量的Type.Name()
是nil
。
示例:
func main() {
type cat struct {
}
p := &cat{}
typeOfCat := reflect.TypeOf(p)
fmt.Printf("type: %v\n", typeOfCat) // reflect.Type是interface类型,所以可传递给fmtr打印
fmt.Printf("TypeName: %v, Kind: %v\n", typeOfCat.Name(), typeOfCat.Kind())
fmt.Printf("TypeName: %v, Kind: %v\n", typeOfCat.Elem().Name(), typeOfCat.Elem().Kind())
}
输出:
type: *main.cat
TypeName: , Kind: ptr
TypeName: cat, Kind: struct
4、结构体反射
如果反射对象是结构体,则有以下方法获得结构体中的成员信息:
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引返回索引对应的结构体字段的信息,当值不是结构体或索引超界时发生宕机 |
NumField() int | 返回结构体成员字段数量,当类型不是结构体或索引超界时发生宕机 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息,没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息,没有找到时返回零值。当类型不是结构体或索引超界时发生宕机 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段,当值不是结构体或索引超界时发生宕机 |
1)StructField
(结构体字段)
reflect.Type
的Field()
方法返回StructField
结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签StructTag
等,而且还可以通过StructField
的Type
字段进一步获取结构体成员的类型信息。
StructField 的结构如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
示例:
func main() {
type cat struct {
Name string
Color string
Age int
Home struct {
Adderess string
PostCode int
}
}
ins := cat{
Name: "Tom",
Color: "Dark",
Age: 5,
Home: struct{
Adderess string
PostCode int
}{
"England",
1024,
},
}
sType := reflect.TypeOf(ins)
for i:=0; i < sType.NumField(); i++ {
filed := sType.Field(i)
fmt.Printf("filedName: %s, Type: %v\n", filed.Name, filed.Type)
}
if addrType, ok := sType.FieldByName("Adderess"); ok {
fmt.Printf("Name: %s, Type: %v", addrType.Name, addrType.Type)
}
}
输出:
filedName: Name, Type: string
filedName: Color, Type: string
filedName: Age, Type: int
// Adderess属于Home这个成员
filedName: Home, Type: struct { Adderess string; PostCode int }
2)StructTag
(结构体标签)
结构体成员信息
reflect.StructField
结构中的Tag
被称为结构体标签StructTag
。结构体标签是对结构体字段的额外信息标签。
type StructTag string
结构体标签的格式
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成;键与值使用冒号分隔,冒号后不能有空格,值用双引号括起来;键值对之间使用一个空格分隔。
从结构体标签中获取值
StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:
func (tag StructTag) Get(key string) string //根据 Tag 中的键获取对应的值。
func (tag StructTag) Lookup(key string) (value string, ok bool) //根据 Tag 中的键,查询值是否存在。
示例:
func test4() {
type cat struct {
Name string `json:"name" bson:"NAME"`
Color string
}
ins := cat{
Name: "Tom",
Color: "Dark",
}
sType := reflect.TypeOf(ins)
if filedName, ok := sType.FieldByName("Name"); ok {
nameJson := filedName.Tag.Get("json")
fmt.Printf("the json tag of Name is: %s\n", nameJson)
}
}
输出:
the json tag of Name is: name
二、relect.Value
reflect.Value
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
// 方法:
pointer() unsafe.Pointer
Addr() Value // 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机
Bool() bool // 将值以 bool 类型返回
Bytes() []byte // 将值以字节数组 []bytes 类型返回
runes() []rune
CanAddr() bool // 表示值是否可寻址
CanSet() bool // 返回值能否被修改。要求值可寻址且是导出的字段
Call(in []Value) []Value
CallSlice(in []Value) []Value
call(op string, in []Value) []Value
Cap() int
Close()
Complex() complex128
Elem() Value // 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回 nil 的 Value
Field(i int) Value
FieldByIndex(index []int) Value
FieldByName(name string) Value
FieldByNameFunc(match func(string) bool) Value
Float() float64 // 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Index(i int) Value
Int() int64 // 将值以 int64 类型返回,所有有符号整型均可以此方式返回,可能需要类型转换
CanInterface() bool
Interface() (i interface{}) // 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
InterfaceData() [2]uintptr
IsNil() bool // 返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作
IsValid() bool // 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。
IsZero() bool
Kind() Kind
Len() int
MapIndex(key Value) Value
MapKeys() []Value
MapRange() *MapIter
Method(i int) Value
NumMethod() int
MethodByName(name string) Value
NumField() int
OverflowComplex(x complex128) bool
OverflowFloat(x float64) bool
OverflowInt(x int64) bool
OverflowUint(x uint64) bool
Pointer() uintptr
Recv() (x Value, ok bool)
recv(nb bool) (val Value, ok bool)
Send(x Value)
send(x Value, nb bool) (selected bool)
Set(x Value)
SetBool(x bool)
SetBytes(x []byte)
setRunes(x []rune)
SetComplex(x complex128)
SetFloat(x float64)
SetInt(x int64)
SetLen(n int)
SetCap(n int)
SetMapIndex(key Value, elem Value)
SetUint(x uint64)
SetPointer(x unsafe.Pointer)
SetString(x string)
Slice(i int, j int) Value
Slice3(i int, j int, k int) Value
String() string // 将值以字符串类型返回
TryRecv() (x Value, ok bool)
TrySend(x Value) bool
Type() Type
Uint() uint64 // 将值以 uint 类型返回,所有无符号整型均可以此方式返回
UnsafeAddr() uintptr
assignTo(context string, dst *rtype, target unsafe.Pointer) Value
Convert(t Type) Value
1、获取特定类型的值
// sclice
Index(i int) Value // 返回第i个元素,如果i越界会崩溃
// struct
NumField() int // 返回字段的数量
Field(i int) Value // 返回第i个字段,如果i越界会崩溃
// map
MapIndex(key Value) Value // 返回key对应的值
// ptr
Elem() Value
// interface
// 先通过 IsNil 判断是否非空, 如果非空, 再通过 Elem() 获取动态值
2、判断Value
中的值是否有效:
// *int的空指针
var a *int
fmt.Println("nil *int IsNil:", reflect.ValueOf(a).IsNil())
fmt.Println("nil *int IsValid:", reflect.ValueOf(nil).IsValid())
// 实例化一个结构体
s := struct{}{}
// 尝试从结构体中查找一个不存在的字段
fmt.Println("不存在的结构体成员 IsValid:", reflect.ValueOf(s).FieldByName("").IsValid())
// 尝试从结构体中查找一个不存在的方法
fmt.Println("不存在的结构体方法 IsValid:", reflect.ValueOf(s).MethodByName("").IsValid())
// 实例化一个map
m := map[int]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("不存在的键 IsValid:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
输出:
nil *int IsNil: true
nil *int IsValid: false
不存在的结构体成员 IsValid: false
不存在的结构体方法 IsValid: false
不存在的键 IsValid: false
3、通过类型信息创建实例
var a int
myType := reflect.TypeOf(a)
myIns := reflect.New(myType) // *int
myIns.Elem().SetInt(15)
fmt.Println(myIns.Type())
fmt.Println(myIns.Elem().Interface())
// 输出
// *int
// 15
4、通过反射调用函数
// 普通函数
func add(a, b int) int {
return a + b
}
func main() {
// 将函数包装为反射值对象
funcValue := reflect.ValueOf(add)
// 构造函数参数, 传入两个整型值
paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
// 反射调用函数
retList := funcValue.Call(paramList)
// 获取第一个返回值, 取整数值
fmt.Println(retList[0].Int())
}
// 调用成功后,通过 retList[0] 取返回值的第一个参数,使用 Int 取返回值的整数值。
// Call(in []Value) []Value
三、反射三定律
1、反射可以将"接口类型变量"转换为"反射类型对象"。
注:这里反射类型指 reflect.Type
和 reflect.Value
。
TypeOf
与ValueOf
的参数是空接口类型,任何变量都可以传入。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
2、反射可以将"反射类型对象"转换为"接口类型变量"。
reflect.Type
本身是接口,所以传给fmt
的函数会打印底层数据。reflect.Value
有interface()
方法。
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
var a int
fmt.Println(reflect.ValueOf(a).Interface())
3、如果要修改反射类型对象
其值必须是可写的
。
Go
语言中类似x
、x.f[1]
和*p
形式的表达式都可以表示变量,但是其它如x + 1
和f(2)
则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。
可写性
最终是由一个反射对象是否存储了原始值而决定的。对于一个不具有可写性
的Value
类型变量,调用Set
方法会报出错误。我们可以通过CanSet()
方法检查一个reflect.Value
类型变量的可写性
。CanAddr()
表示可寻址
,可寻址的Value
不一定可写,还需要判断是否可导出。比如,一个结构体中的私有成员就是不可导出的(unexported field)。
示例:
var a int = 3
fmt.Printf("a CanAddr: %v\n", reflect.ValueOf(a).CanAddr())
v := reflect.ValueOf(&a).Elem()
fmt.Printf("v CanAddr: %v\n", v.CanAddr())
fmt.Printf("v CanSet: %v\n", v.CanSet())
v.SetInt(1)
fmt.Println(a)
输出:
a CanAddr: false
v CanAddr: true
v CanSet: true
1