theme: smartblue
highlight: vs2015
🔰 全文字数 : 8K+
🕒 阅读时长 : 10min
📋 关键词汇 : golang / reflect
👉 欢迎关注 : 大摩羯先生
GolangReflectGolang
众所周知,编程语言都是依靠一定的组织结构来构建的,比如代码块、类、方法、字段等,而这里面最原子的表达单位就是承载一个个数据传递的变量,变量又会按照特定的数据格式表示为字符串、数值型、布尔型等各种类型,我们可以用字符串描述一段话,用数值来实现数学运算,用布尔表示是或否,在利用编程语言提供的不同类型构建编码时,其实都是对现实世界的一种抽象和映射,而且这一切都是建立在明确的意图表达上的,这种明确的意图表达能否让我们选择编程语言提供的具体的某一个数据类型来匹配。
特别的是,现实世界里的诉求是复杂的,有很多时候我们并不能提前明确意图,比如我们要实现一个收集用户信息的方法,最开始我们只定义允许用户传入一个字符串来传递姓名即可,如下:
func CollectUserInfoV1(name string) {
fmt.Println("姓名:", name)
}
随着需求的丰富,我们还要收集用户的年龄、住址、电话等信息,于是我们可以把这些信息封装到一个结构体中,直接传递这个结构体,如下:
func CollectUserInfoV2(user *User) {
fmt.Println("姓名:", user.Name)
fmt.Println("年龄:", user.Age)
fmt.Println("住址:", user.Address)
fmt.Println("电话:", user.Phone)
}
现在用户信息丰富了,之后还会有批量诉求,要支持传入批量,于是改造如下:
func CollectUserInfoV3(users []*User) {
for _, user := range users {
fmt.Println("姓名:", user.Name)
fmt.Println("年龄:", user.Age)
fmt.Println("住址:", user.Address)
fmt.Println("电话:", user.Phone)
}
}
KeyValueMap
Reflect
func CollectUserInfo(param interface{}) {
val := reflect.ValueOf(param)
switch val.Kind() {
case reflect.String:
fmt.Println("姓名:", val.String())
case reflect.Struct:
fmt.Println("姓名:", val.FieldByName("Name"))
fmt.Println("年龄:", val.FieldByName("Age"))
fmt.Println("住址:", val.FieldByName("Address"))
fmt.Println("电话:", val.FieldByName("Phone"))
case reflect.Ptr:
fmt.Println("姓名:", val.Elem().FieldByName("Name"))
fmt.Println("年龄:", val.Elem().FieldByName("Age"))
fmt.Println("住址:", val.Elem().FieldByName("Address"))
fmt.Println("电话:", val.Elem().FieldByName("Phone"))
case reflect.Array, reflect.Slice:
for i := 0; i < val.Len(); i++ {
fmt.Println("姓名:", val.Index(i).FieldByName("Name"))
fmt.Println("年龄:", val.Index(i).FieldByName("Age"))
fmt.Println("住址:", val.Index(i).FieldByName("Address"))
fmt.Println("电话:", val.Index(i).FieldByName("Phone"))
}
case reflect.Map:
itr := val.MapRange()
for itr.Next() {
fmt.Println("用户ID:", itr.Key())
fmt.Println("姓名:", itr.Value().Elem().FieldByName("Name"))
fmt.Println("年龄:", itr.Value().Elem().FieldByName("Age"))
fmt.Println("住址:", itr.Value().Elem().FieldByName("Address"))
fmt.Println("电话:", itr.Value().Elem().FieldByName("Phone"))
}
default:
panic("unsupport type !!!")
}
}
func TestCollectUserInfo(t *testing.T) {
//string
CollectUserInfo("张三")
// 姓名: 张三
//Struct
CollectUserInfo(User{
Name: "张三",
Age: 20,
Address: "北京市海淀区",
Phone: 1234567,
})
//姓名: 张三
//年龄: 20
//住址: 北京市海淀区
//电话: 1234567
//Ptr
CollectUserInfo(&User{
Name: "张三",
Age: 20,
Address: "北京市海淀区",
Phone: 1234567,
})
//姓名: 张三
//年龄: 20
//住址: 北京市海淀区
//电话: 1234567
//Slice
CollectUserInfo([]User{
{
Name: "张三",
Age: 20,
Address: "北京市海淀区",
Phone: 1234567,
},
{
Name: "李四",
Age: 30,
Address: "北京市朝阳区",
Phone: 7654321,
},
})
//姓名: 张三
//年龄: 20
//住址: 北京市海淀区
//电话: 1234567
//姓名: 李四
//年龄: 30
//住址: 北京市朝阳区
//电话: 7654321
//Array
CollectUserInfo([2]User{
{
Name: "张三",
Age: 20,
Address: "北京市海淀区",
Phone: 1234567,
},
{
Name: "李四",
Age: 30,
Address: "北京市朝阳区",
Phone: 7654321,
},
})
//姓名: 张三
//年龄: 20
//住址: 北京市海淀区
//电话: 1234567
//姓名: 李四
//年龄: 30
//住址: 北京市朝阳区
//电话: 7654321
CollectUserInfo(map[int]*User{
1: {
Name: "张三",
Age: 20,
Address: "北京市海淀区",
Phone: 1234567,
},
2: {
Name: "李四",
Age: 30,
Address: "北京市朝阳区",
Phone: 7654321,
},
})
//用户ID: 1
//姓名: 张三
//年龄: 20
//住址: 北京市海淀区
//电话: 1234567
//用户ID: 2
//姓名: 李四
//年龄: 30
//住址: 北京市朝阳区
//电话: 7654321
}
这样,我们把所有类型的参数都封装到一个函数方法中,未来的变化,扩展化外部是不需要感知的,有没有体会到反射功能的强大呢!
Go1.16reflect
reflect
interface{}TypeOfTypeValueOfValue
interface{}TypeValueGolanginterface{}
type.go
Typertype
Type
Type
// Type类型值是可比较的,例如使用==运算符,因此它们可以用作映射键。
// 如果两个类型值表示相同的类型,则它们相等。
type Type interface {
// 适用于所有类型的方法.
// Align在内存中分配时返回此类型值的字节对齐方式
Align() int
// FieldAlign在结构中用作字段时,返回此类型值的字节对齐方式
FieldAlign() int
// 方法返回类型的方法集中的第i个方法。如果不在范围[0,NumMethod())内,会导致panic.
// 对于非接口类型T或*T,返回的方法的type和Func字段描述其第一个参数为接收器的函数,并且只有导出的方法才可访问
// 对于接口类型,返回方法的类型字段给出了方法签名,没有接收器,而Func字段为零
// 方法按字典顺序排序
Method(int) Method
// MethodByName返回在类型的方法集中具有该名称的方法,并返回一个布尔值,指示是否找到该方法
// 对于非接口类型T或*T,返回方法的type和Func字段描述其第一个参数为接收器的函数
// 对于接口类型,返回的方法的类型字段给出了方法签名,没有接收器,而Func字段为零
MethodByName(string) (Method, bool)
// NumMethod返回使用方法可访问的方法数
// 注意,NumMethod仅对接口类型统计未导出的方法
NumMethod() int
// Name返回定义类型的包中的类型名称
// 对于其他(未定义)类型,它返回空字符串
Name() string
// PkgPath返回定义类型的包路径,即唯一标识包的导入路径,例如“encoding/base64”
// 如果类型已预先声明(字符串、错误)或未定义(*T、struct{}、[]int或A,其中A是未定义类型的别名),则包路径将为空字符串
PkgPath() string
// Size返回存储给定类型的值所需的字节数;这类似于unsafe.Sizeof()的功能
Size() uintptr
// String返回类型的字符串表示形式。
// 字符串表示可以使用缩短的包名(例如,base64而不是“encoding/base64”),并且不能保证在类型中是唯一的。要测试类型标识,请直接比较类型。
String() string
// Kind返回此类型的特定种类
Kind() Kind
// 报告类型是否实现接口类型u
Implements(u Type) bool
// 报告类型的值是否可分配给类型u
AssignableTo(u Type) bool
// 报告类型的值是否可转换为u类型
ConvertibleTo(u Type) bool
// 报告此类型的值是否可比较
Comparable() bool
// 仅适用于某些类型的Method取决于Kind
// 每个Kind类型允许的方法如下:
// 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
// Bits返回类型的大小(以位为单位)
// 如果类型的种类不是大小为或未大小为Int、Uint、Float或Complex的种类之一,则会导致panic。
Bits() int
// ChanDir返回通道类型的方向
// 如果类型不是Chan,它会panic.
ChanDir() ChanDir
// IsVariadic报告函数类型的最终输入参数是否为“…”参数如果是,t.In(t.NumIn()-1)返回参数的隐式实际类型[]T
// 具体举例,如果t表示func(x int,y…float64),则
// t.NumIn() == 2
// t.In(0) is the reflect.Type for "int"
// t.In(1) is the reflect.Type for "[]float64"
// t.IsVariadic() == true
// 如果类型的种类不是Func会panic
IsVariadic() bool
// Elem返回类型的元素类型
// 如果类型的种类不是Array、Chan、Map、Ptr或Slice,会出现panic
Elem() Type
// 字段返回结构类型的第i个字段
// 如果类型的种类不是Struct,它就会panic
// 如果不在范围[0,NumField())内,它就会panic
Field(i int) StructField
// FieldByIndex返回与索引序列对应的嵌套字段。它相当于为每个索引i依次调用字段
// 如果类型的种类不是Struct,它就会panic
FieldByIndex(index []int) StructField
// FieldByName返回具有给定名称和布尔值的结构字段,该布尔值指示是否找到该字段
FieldByName(name string) (StructField, bool)
// FieldByNameFunc返回结构字段,其名称满足匹配函数,布尔值指示是否找到该字段
// FieldByNameFunc首先考虑结构本身中的字段,然后考虑任何嵌入结构中的字段,按广度优先顺序,在包含一个或多个满足匹配函数的字段的最浅嵌套深度处停止
// 如果该深度的多个字段满足match函数,则它们会相互抵消,FieldByNameFunc不会返回匹配
// 此行为反映了Go在包含嵌入字段的结构中处理名称查找的方式
FieldByNameFunc(match func(string) bool) (StructField, bool)
// In返回函数类型的第i个输入参数的类型
// 如果类型的种类不是Func,它就会panic
// 如果不在范围[0,NumIn())内,它就会panic
In(i int) Type
// Key返回映射类型的键类型
// 如果类型的类型不是Map,它会感到panic
Key() Type
// Len返回数组类型的长度
// 如果类型的种类不是数组,它就会panic
Len() int
// NumField返回结构类型的字段计数
// 如果类型的种类不是Struct,它就会panic
NumField() int
// NumIn返回函数类型的输入参数计数
// 如果类型的种类不是Func,它就会panic
NumIn() int
// NumOut返回函数类型的输出参数计数
// 如果类型的种类不是Func,它就会panic
NumOut() int
// Out返回函数类型的第i个输出参数的类型
// 如果类型的种类不是Func,它就会panic
// 如果不在范围[0,NumOut())内,它就会panic
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
Type
Type
Typetype Kind uintGolang
type Kind uint
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
)
TypeKindpanic
Type
rtype
rtype
type rtype struct {
size uintptr
ptrdata uintptr // rtype可以包含指针的字节数
hash uint32 // rtype哈希值;避免哈希表中的计算
tflag tflag // 额外的类型信息标识
align uint8 // 当前具体类型变量的内存对齐
fieldAlign uint8 // 当前具体类型结构体字段的内存对齐
kind uint8 // 具体Kind的枚举值
// 当前具体类型使用的对比方法
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // 垃圾回收数据
str nameOff // 字符串格式
ptrToThis typeOff // 指向此类型的指针的类型,可以为零
}
value.go
type Value structValueValue
// Value是Go值的反射.
// 并非所有方法都适用于所有类型的值。每种方法的文档中都注明了限制条件(如有)。
// 在调用特定于种类的方法之前,请使用种类方法找出值的种类。调用不适合该类型的方法会导致运行时panic
// 零值代表未赋值、空值
// 零值的IsValid方法返回false,其Kind方法返回Invalid,其String方法返回“<Invalid Value>”,所有其他方法都无法使用
// 大多数函数和方法从不返回无效值
// 如果有,其文档将明确说明这些条件
// 一个值可以由多个goroutine同时使用,前提是基础Go值可以同时用于等效的直接操作
// 要比较两个值,请比较接口方法的结果。在两个值上使用==不会比较它们表示的基础值
type Value struct {
// typ保存由值表示的值的类型。
typ *rtype
// 指针值数据,或者,如果设置了flagIndir,则为指向数据的指针
// 设置flagIndir或typ.pointers()为true
// 这是非常核心的数据,可以把它理解为具体数据的内存位置所在,数据的类型表达依赖它来转换
ptr unsafe.Pointer
// flag是一个标志位,通过二进制的方式保存了关于值的元数据
// 最低位是标志位!最低的五位给出了值的类型,代表Kind的枚举的二进制,一共是27个,用5位表示,其余依次如下:
// - flagStickyRO: 代表不能导出的非嵌入字段获取,因此为只读
// - flagEmbedRO: 代表不能导出的嵌入字段获取,因此为只读
// - flagIndir: 代表持有指向数据的指针
// - flagAddr: 代表CanAddr方法的返回值标记
// - flagMethod: 代表是否为一个方法的标记
// 剩余的高23位给出了方法值的方法编号。
// 如果flag.Kind()!=Func,代码可以假设flagMethod未设置。
// 如果ifaceIndir(typ)为真,则代码可以假设设置了flagIndir。
flag
// 方法值表示当前的方法调用,就像接收者r调用r.Read方法。typ+val+flag的标志位描述r的话,但flag的Kind标志位表示Func(方法是函数),flag的高位表示r类型的方法列表中的方法编号
}
typ(*rtype)ptr(unsafe.Pointer)flag(uintptr)
flag
type flag uintptr
const (
flagKindWidth = 5 // 有27个Kind类型,5位可以容纳2^5可以表示32个类型
flagKindMask flag = 1<<flagKindWidth - 1
flagStickyRO flag = 1 << 5
flagEmbedRO flag = 1 << 6
flagIndir flag = 1 << 7
flagAddr flag = 1 << 8
flagMethod flag = 1 << 9
flagMethodShift = 10
flagRO flag = flagStickyRO | flagEmbedRO
)
类似地,Value也提供了通用化或差异化的能力定义支持:
makefunc.go
makefunc
// MakeFunc返回一个给定类型的新函数,该函数封装了函数fn。调用时,该新函数执行以下操作:
// - 将其参数转换为一个切片Slice的值。
// - 运行结果:=fn(args)。
// - 将结果作为一个切片Slice的值返回,每个正式结果一个值。
// 实现fn可以假设参数值切片具有typ给定的参数数量和类型。
// 如果typ描述可变函数,则最终值本身是表示可变参数的切片,如在可变函数体中。
// fn返回的结果值切片必须具有typ给定的结果数量和类型。
// Value.Call方法允许调用方根据值调用类型化函数;相反,MakeFunc允许调用方根据值实现类型化函数。
// 文档的示例部分演示了如何使用MakeFunc为不同类型构建交换函数
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
if typ.Kind() != Func {
panic("reflect: call of MakeFunc with non-Func type")
}
t := typ.common()
ftyp := (*funcType)(unsafe.Pointer(t))
// Go func的间接值(虚拟)以获取实际代码地址。
// Go func值是指向C函数指针的指针。https://golang.org/s/go11func
dummy := makeFuncStub
code := **(**uintptr)(unsafe.Pointer(&dummy))
// makeFuncImpl包含一个堆栈映射,供运行时使用
_, argLen, _, stack, _ := funcLayout(ftyp, nil)
impl := &makeFuncImpl{code: code, stack: stack, argLen: argLen, ftyp: ftyp, fn: fn}
return Value{t, unsafe.Pointer(impl), flag(Func)}
}
swapper.go
SliceReflectReflect
func TestSwapper(t *testing.T) {
s := []int{1,2,3,4,5} // 声明一个切片,元素排列为 [1 2 3 4 5]
f := reflect.Swapper(s) // 调用reflect.Swapper()方法,出参是一个方法
f(0,1) // 调用方法,将索引位 0、1的元素互换
fmt.Println(s) // 结果为[2 1 3 4 5]
}
deepequal.go
ReflectReflect
func TestDeepEqual(t *testing.T) {
x := &User{Name: "zhangsan", Age: 10}
y := &User{Name: "zhangsan", Age: 10}
fmt.Println(reflect.DeepEqual(x, y)) // true
x1 := &User{Name: "zhangsan", Age: 10}
y1 := &User{Name: "zhangsan", Age: 0}
fmt.Println(reflect.DeepEqual(x1, y1)) // false
x2 := map[string]int{"zhangsan": 10}
y2 := map[string]int{"zhangsan": 10}
fmt.Println(reflect.DeepEqual(x2, y2)) // true
x3 := map[string]int{"zhangsan": 10}
y3 := map[string]int{"lisi": 10}
fmt.Println(reflect.DeepEqual(x3, y3)) // false
}
Go
Reflection goes from interface value to reflection object
反射可以将 “接口类型变量” 转换为 “反射类型对象”
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
func TestInterface2ReflectionObj(t *testing.T) {
f := float32(3.14)
// Interface 转换为 Type
typ := reflect.TypeOf(f)
// Interface 转换为 Value
val := reflect.ValueOf(f)
fmt.Println(typ) // float32
fmt.Println(val) // 3.14
}
reflect.TypeOfreflect.ValueOf
Reflection goes from reflection object to interface value
反射可以将 “反射类型对象” 转换为 “接口类型变量”
func (v Value) Interface() (i interface{})
func TestReflectionObj2Interface(t *testing.T) {
f := float32(3.14)
// 通过Reflect对象转换Interface
val := reflect.ValueOf(f)
// 转换指定的Interface
fmt.Printf("%T %f \n",val.Interface().(float32),val.Interface().(float32)) // float32 3.140000
}
To modify a reflection object, the value must be settable
想要修改反射对象,它的值必须是可赋值的(可写的)
Value.CanSet()flagAddr
func TestSettable(t *testing.T) {
//****可赋值****
//声明一个对象,并初始化赋值
user := &User{Name: "zhangsan",Age: 10}
// 判断是否可赋值
fmt.Println(reflect.ValueOf(user).CanSet()) // false
fmt.Println(reflect.ValueOf(user).Elem().CanSet()) // true
// 获取对象user中Name字段的值
fmt.Println(reflect.ValueOf(user).Elem().FieldByName("Name")) // zhangsan
// 获取对象user中Age字段的值
fmt.Println(reflect.ValueOf(user).Elem().FieldByName("Age")) // 10
reflect.ValueOf(user).Elem().Field(0).SetString("lisi")
reflect.ValueOf(user).Elem().Field(1).SetInt(20)
fmt.Println(user) // &{lisi 20}
//****不可赋值****
f := float32(3.14)
// 这里的f是一个临时变量声明,由于安全性和变更的未知性带来的潜在问题,这里是不可寻址的
fmt.Println(reflect.ValueOf(f), reflect.ValueOf(f).CanSet()) // 3.14 false
// &f是指向变量f的指针变量,对于此和f的变量的不可寻址本质是一样的
fmt.Println(reflect.ValueOf(&f), reflect.ValueOf(&f).CanSet()) // 0xc0000182e8 false
//****可赋值****
//这里调用Elem主要是复制了&f地址空间,并使返回的Elem(&f)变为可赋值!
fmt.Println(reflect.ValueOf(&f).Elem(), reflect.ValueOf(&f).Elem().CanSet()) // 3.14 true
}
Value.Elem()ValueflagAddrflagAddr
encoding/jsonReflect
// newTypeEncoder constructs an encoderFunc for a type.
// The returned encoder only checks CanAddr when allowAddr is true.
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
// If we have a non-pointer value whose type implements
// Marshaler with a value receiver, then we're better off taking
// the address of the value - otherwise we end up with an
// allocation as we cast the value to an interface.
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(marshalerType) {
return marshalerEncoder
}
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(textMarshalerType) {
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(textMarshalerType) {
return textMarshalerEncoder
}
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
}
}
Golang
GolangGolanginterface{}字符串、数值、布尔”成品“reflect半成品reflectTypeOf、ValueOfinterface{}字符串、数值、布尔Map、结构体、数组、切片偏移量
优点
- 避免硬编码,提供灵活性和通用性
- 可以作为第一类对象发现并修改源码结构(代码块、类、方法、协议等)
缺点
interfacepanic
Golang标准库文档
Go夜谈 #108 Golang 反射应用及源码分析
Go官方反射介绍
《Go语言圣经》反射章节 手摸手Go 接口与反射