目录
前言
书上对go反射的讲解太浅显,网上的博客也太零散,想着自己整理一篇博客吧,为解决当前的一个问题,也为以后复习参考。
思考题
在读这篇博客之前可以先想象这样一个情景:自己设计一个类似 json.Marshal(v interface{})的函数,按顺序递归打印出入参的每一个字段的名称和值。
这也是我当前遇到的需求的一个环节。
反射的三大法则及go中对应的实现
- 能根据接口数据获得反射对象。对应go中reflect.TypeOf(i interface{})和reflect.ValueOf(i interface{})两个方法。
- 能根据反射对象获得接口数据。对应go中Value.Interface()、Value.[基本数据类型]()等方法。
- 如果数据可修改,则能通过反射对象修改。对应go中Value.CanSet()和Value的Set系列方法。
go反射概述
- go不支持解析string然后执行。golang的反射机制只能存在于已经存在的对象上面。
- go的反射主要由Type和Value组成。可分别由reflect.TypeOf(i interface{})和reflect.ValueOf(i interface{})获得。Type是一个接口,有若干函数,reflect.TypeOf(i interface{})返回的是实现了这个接口的具体实现。Value是一个结构体,有若干字段和函数。
- Type也可由Value.Type()获得,Value不可由Type获得。
- Type存储接口的类型信息,例如方法数量、方法名、字段数量、字段名等。当使用Type时应当只读取信息,不能试图通过Type进行修改、调用之类的操作。
- Value存储接口的数据信息,并且包含对应的Type,可以通过Value.Type()获得对应的Type。
Type详解
通过go中Type的定义,可以看出Type及其具体实现有哪些方法可以用。下面给几个常用方法做了注释
type Type interface {
Name() string //返回类型名
Kind() Kind //返回该类型的种类,Array、Slice、Struct等
Elem() Type //当type当kind是Array, Chan, Map, Ptr, 或 Slice使用,返回类型的元素类型。
Method(int) Method // 按下标返回Method,超出范围Panic
MethodByName(string) (Method, bool) // 按方法名称返回Method和表示方法是否存在的bool
NumMethod() int // 返回方法数量
NumIn() int //当kind是Func时,返回入参数量
In(i int) Type //当Kind是Func时,返回第i个入参类型
NumOut() int //当kind是Func时,返回出参数量
Out(i int) Type //当kind是Func时,返回第i个出参类型
NumField() int //当Kind是Struct时,返回字段数量,内嵌结构体算一个。
Field(i int) StructField //当Kind是Struct时,通过下标返回字段
FieldByIndex(index []int) StructField //当Kind是Struct时,返回内嵌结构体字段
FieldByName(name string) (StructField, bool) //当Kind是Struct时,通过名称返回字段
FieldByNameFunc(match func(string) bool) (StructField, bool) //当Kind是Struct时,按自定义字段名匹配函数返回字段
Key() Type //当Kind时map时,返回键的类型,值的类型可由Elem()返回
Len() int //当类型是Array时,返回长度
PkgPath() string //返回定义类型的包的路径
Size() uintptr //返回存储一个该类型的值所需要的字节数
Align() int
FieldAlign() int
String() string
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
Bits() int
ChanDir() ChanDir
IsVariadic() bool
common() *rtype
uncommon() *uncommonType
}
Method
Type中Method(int) Method、MethodByName(string) (Method, bool)、NumMethod() int三个方法与Method有关,Method是一个结构体,存储了方法名、方法在Type方法集中的下标等信息。
type Method struct {
Name string //方法名
PkgPath string
Type Type
Func Value
Index int // 在Type方法集中的下标
}
示例代码:
func (user User) GetId() int {
log.Println("[User] GetId begin")
log.Printf("[User] age:%d", user.age)
log.Println("[User] GetId end")
return user.age
}
func main() {
user := User{
Name: "张三",
age: 18,
}
userType := reflect.TypeOf(user)
log.Printf("userType.NumMethod():%d", userType.NumMethod()) //1
method, exist := userType.MethodByName("GetId")
if exist {
log.Printf("method.Name:%+v", method.Name) //GetId
log.Printf("method.PkgPath:%+v", method.PkgPath) //
log.Printf("method.Type:%+v", method.Type) //func(main.User) int
log.Printf("method.Func:%+v", method.Func) //0x10bd9f0
log.Printf("method.Index:%+v", method.Index) //0
}
for i := 0; i < userType.NumMethod(); i++ {
method := userType.Method(i)
log.Printf("method.Name:%+v", method.Name) //GetId
log.Printf("method.PkgPath:%+v", method.PkgPath) //
log.Printf("method.Type:%+v", method.Type) //func(main.User) int
log.Printf("method.Func:%+v", method.Func) //0x10bd9f0
log.Printf("method.Index:%+v", method.Index) //i
}
}
Kind
Type的Kind()方法返回该类型的种类,可根据不同的种类进行不同的操作。reflect中枚举的种类有以下几种:
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
Ptr
Slice
String
Struct
UnsafePointer
)
StructField
先通过一个例子说明一下FieldByIndex(index []int) StructField函数
type Point struct {
X int
Y int
Z int
}
type User struct {
Name string
Age int
Point
}
func main() {
user := User{
Name: "张三",
Age: 18,
}
userType := reflect.TypeOf(user)
index := []int{2, 1} //代表下标为2的字段是一个内嵌结构体,取出这个内嵌结构体中下标为1的字段
field := userType.FieldByIndex(index)
log.Printf("userType.FieldByIndex():%+v", field)
}
StructField记录结构体的字段信息,包括字段名称、字段标签、字段是否为内嵌结构体、字段在结构体中的偏移量等。
type StructField struct {
Name string //名称
PkgPath string
Type Type //类型
Tag StructTag //标签
Offset uintptr //如果是内嵌结构体里的字段,则从内嵌结构体里面重新计算偏移量
Index []int //如果是内嵌结构体里的字段,则从内嵌结构体里面重新计数下标
Anonymous bool //是否是内嵌结构体
}
Value详解
先看一下Value的定义:
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
可以看到Value只有三个非导出字段,这表面我们对Value的一切操作都要通过调用函数。值得注意的是typ *rtype字段,这是Value对应的Type实例。我们可以通过Value.Type()获得这个实例。
Value有61个可用函数。下面分类看看这些函数
比较通用的函数
func (v Value) CanAddr() bool //判断v是否可寻址。如果v是切片元素、可寻址数组元素、可寻址结构体字段、或ptr执行Elem()后返回的结果,那么v是可寻址的。
func (v Value) CanSet() bool //判断v是否可修改。可当v寻址且是导出字段时可修改。
func (v Value) Elem() Value //当v的kind是Interface 或 Ptr,返回v指向的实例。
func (v Value) Addr() Value //当v可寻址是返回v当指针Value,和Elem()互为逆操作。
func (v Value) Kind() Kind //返回v的Kind。
func (v Value) IsNil() bool //当v的kind是Chan, Func, Map, Ptr, UnsafePointer时,判断v是否是nil
func (v Value) IsValid() bool //判断v是否有效
func (v Value) IsZero() bool //判断v是否是它所属类型的零值
func (v Value) Pointer() uintptr //当v的 Kind is not Chan, Func, Map, Ptr, Slice, or UnsafePointer时使用。
func (v Value) UnsafeAddr() uintptr //返回指向v的数据的指针
函数
func (v Value) NumMethod() int //返回v的函数数量。
func (v Value) Method(i int) Value //按下标返回函数。
func (v Value) MethodByName(name string) Value //按名字返回函数。
func (v Value) Call(in []Value) []Value //当v的Kind是Func时,执行函数v。in是入参,返回是出参。
func (v Value) CallSlice(in []Value) []Value //当vKind是Func且是变参函数时,执行函数。in是入参,返回是出参。
数组、切片、字符串
func (v Value) Cap() int //当v的Kind是Array、Slice、Chan时返回v的容量。
func (v Value) Len() int //当v的Kind是Array、Slice、String、Chan、Map时返回v的长度。
func (v Value) Index(i int) Value //当v的Kind是Array、Slice、String时按下标返回元素。
func (v Value) Slice(i, j int) Value //当v的Kind是Array、Slice、String时,返回v[i:j]
func (v Value) Slice3(i, j, k int) Value //当v的Kind是Array、Slice时,返回v[i:j:k]
通道
func (v Value) Len() int
func (v Value) Cap() int
func (v Value) Recv() (x Value, ok bool)
func (v Value) Send(x Value)
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Close() //当v的kind是Chan时,关闭v。
映射
func (v Value) Len() int
func (v Value) MapKeys() []Value //当v的Kind是Map时,返回键数组
func (v Value) MapIndex(key Value) Value //当v的Kind是Map时,输入键返回值。
func (v Value) MapRange() *MapIter //当v的Kind是Map时,返回MapIter实例。
MapIter是遍历Map的迭代器,它的字段都是非导出字段,我们只需要关心MapIter的方法:
func (it *MapIter) Key() Value
func (it *MapIter) Value() Value
func (it *MapIter) Next() bool
结构体
func (v Value) Field(i int) Value //当v的kind是Struct时,按下标返回字段
func (v Value) FieldByIndex(index []int) Value //当v的kind是Struct时,返回内嵌字段
func (v Value) FieldByName(name string) Value //当v的kind是Struct时,按名字返回字段
func (v Value) FieldByNameFunc(match func(string) bool) Value //当v的kind是Struct时,按自定义字段名匹配规则返回字段。
从反射类型得到接口数据
从反射类型转化接口数据的方法,对应反射的第二法则。结构体可先转化为接口再进行断言。基本数据类型和[]byte、[]rune有一步到位的方法。
func (v Value) Interface() (i interface{})
func (v Value) Bool() bool
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) String()
func (v Value) Float() float64
func (v Value) Complex() complex128
func (v Value) Bytes() []byte
func (v Value) runes() []rune
Setter函数
func (v Value) Set(x Value)
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) setRunes(x []rune)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetLen(n int)
func (v Value) SetCap(n int)
func (v Value) SetMapIndex(key, elem Value)
func (v Value) SetUint(x uint64)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
Overflow一类的函数
func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool
其他函数
// CanInterface reports whether Interface can be used without panicking.
func (v Value) CanInterface() bool
// InterfaceData returns the interface v's value as a uintptr pair.
// It panics if v's Kind is not Interface.
func (v Value) InterfaceData() [2]uintptr
// Convert returns the value v converted to type t.
// If the usual Go conversion rules do not allow conversion
// of the value v to type t, Convert panics.
func (v Value) Convert(t Type) Value
go反射最佳实践
从reflect.ValueOf开始
从上文中可以看出,Type能做的事Value基本都能做,Value不能做的事可以通过Value.Type()获得对应的Type再做,所以一般反射可以从reflect.ValueOf开始。如果只需要读取结构信息,可以从reflect.TypeOf开始。
如果想修改数据
在go语言中可以被修改的数据必须满足以下两个条件:
- 可以被寻址
- 被导出
第一个条件很好理解,毕竟要修改数据的值,就得找到数据所在的地址。那什么样的变量才叫可寻址呢?分两步走。
第一步:reflect.ValueOf(i interface{})函数要传入指针类型变量。
第二步:对reflect.ValueOf获得的对象执行Elem()函数。Elem()函数返回的Value就是可寻址的。示例代码:
func main() {
i := 1
v1 := reflect.ValueOf(&i)
v2 := v1.Elem()
//可用Value.CanAddr()判断是不是可以被寻址。
fmt.Println(v1.CanAddr()) // false
fmt.Println(v2.CanAddr()) // true
}
所以如果想通过反射修改数据,就向reflect.ValueOf传入一个指针。
如果只想通过反射读取一些信息,为了避免误改,就向reflect.ValueOf传入一份拷贝。当传入非指针实例时go会自动拷贝数据。
Elem()的替代函数
func (v Value) Elem() Value函数不是任何Value都能调用的,如果v的 Kind 不是 Interface 或 Ptr就会导致Panic。我们遇到的大多数需要执行Elem()的时候都是v的 Kind是Ptr的时候。为了避免每次都手动判断v的Kind是不是Ptr,可以借助reflect包里的func Indirect(v Value) Value函数。这个函数会自行判断v的是不是Kind是不是Ptr,如果是则返回v.Elem(),否则返回v本身。
最初的思考
现在再看文章刚开始的思考题,似乎已经没那么难了。下面贴出来代码,有兴趣的可以看看。
package main
import (
"log"
"reflect"
)
type People struct {
Name string
Age int
}
type User struct {
Id int64
People
friends []*People
ext map[string]interface{}
}
func main() {
user := User{
Id: 1,
People: People{
Name: "张三",
Age: 18,
},
friends: []*People{
{
Name: "李四",
Age: 18,
},
{
Name: "朱五",
Age: 18,
},
},
ext: map[string]interface{}{
"teacher": People{
Name: "杨六",
Age: 36,
},
},
}
Handle(user)
}
func Handle(i interface{}) {
val := reflect.ValueOf(i)
route(val)
}
func route(val reflect.Value) {
if !val.IsValid() {
return
}
switch val.Kind() {
case reflect.Struct:
handleStruct(val)
case reflect.Slice:
handleSlice(val)
case reflect.Map:
handleMap(val)
case reflect.Ptr, reflect.Interface:
route(val.Elem())
default:
log.Printf("处理能力之外的Kind,Kind:%+v", val.Kind())
}
}
func handleStruct(obj reflect.Value) {
objType := obj.Type()
for i := 0; i < obj.NumField(); i++ {
field := obj.Field(i)
switch field.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Struct:
route(field)
default:
log.Printf("%s:%v", objType.Field(i).Name, field)
}
}
}
func handleSlice(list reflect.Value) {
for i := 0; i < list.Len(); i++ {
element := list.Index(i)
route(element)
}
}
func handleMap(m reflect.Value) {
for iter := m.MapRange(); iter.Next(); {
route(iter.Value())
}
}
参考文章
go源码