golang 反射
Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,有静态那么有没有动态呢??
说起动态目前也就只能想起 接口Interface;在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:(value, type)--value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。
什么是反射?
--->反射就是程序能够在运行时检查变量和值,求出它们的类型??------什么意思----??自古以来越是简单描述的东西越难懂,但是理解后又是豁然开朗。
在 Golang中,reflect
实现了运行时反射。reflect
包会帮助识别 interface{}
变量的底层具体类型和具体值。
reflect.Type 和 reflect.Value
reflect.Type
表示 interface{}
的具体类型,而 reflect.Value
表示它的具体值。reflect.TypeOf()
和 reflect.ValueOf()
两个函数可以分别返回 reflect.Type
和 reflect.Value
。这两种类型是我们创建查询生成器的基础;
reflect
包中还有一个重要的类型:Kind
。
在反射包中,Kind
和 Type
的类型可能看起来很相似,
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery(q interface{}) { t := reflect.TypeOf(q) v := reflect.ValueOf(q) fmt.Println("Type ", t) fmt.Println("Value ", v) }
func createQuery_kind(q interface{}) {
t := reflect.TypeOf(q)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Kind ", k)
}
func main() { o := order{ ordId: 456, customerId: 56, } createQuery(o) } -----------------------------------结果--- Type main.order Value {456 56}
Type main.order
Kind struct
Value 的NumField() 和 Field() 方法
NumField()
方法返回结构体中字段的数量,而 Field(i int)
方法返回字段 i
的 reflect.Value
。
Int() 和 String() 方法
Int
和 String
可以帮助我们分别取出 reflect.Value
作为 int64
和 string
。
package main
import (
"fmt"
"reflect"
"strings"
)
type Address struct {
City string `json:"city" xml:"city"`
State string `json:"state" xml:"state"`
}
type Person struct {
Name string `json:"name_index" xml:"name_index"`
Age int `json:"age_index" xml:"age_index"`
Address Address `json:"address_index" xml:"address_index"`
}
func main() {
// 创建一个 Person 实例
p := Person{Name: "Alice", Age: 30, Address: Address{City: "New York", State: "NY"}}
v := reflect.ValueOf(p)
// NumField: 获取字段数量
numFields := v.NumField()
fmt.Println("Number of fields:", numFields)
// Field: 获取指定索引处的字段
nameField := v.Field(0)
fmt.Println("Field 0 (Name):", nameField)
ageField := v.Field(1)
fmt.Println("Field 1 (Age):", ageField)
// FieldByIndex: 获取嵌套字段
cityField := v.FieldByIndex([]int{2, 0})
fmt.Println("Field Address.City:", cityField)
stateField := v.FieldByIndex([]int{2, 1})
fmt.Println("Field Address.State:", stateField)
// FieldByName: 根据字段名获取字段
nameFieldByName := v.FieldByName("Name")
fmt.Println("Field 'Name':", nameFieldByName)
ageFieldByName := v.FieldByName("Age")
fmt.Println("Field 'Age':", ageFieldByName)
addressFieldByName := v.FieldByName("Address")
fmt.Println("Field 'Address':", addressFieldByName)
// FieldByNameFunc: 根据自定义函数获取字段
fullNameField := v.FieldByNameFunc(func(name string) bool {
return strings.HasPrefix(name, "Nam")
})
fmt.Println("Field with prefix 'Nam':", fullNameField)
t := reflect.TypeOf(p)
// NumField: 获取字段数量
numFields = t.NumField()
fmt.Println("Number of fields:", numFields)
// 遍历结构体的字段并打印字段名称
for i := 0; i < numFields; i++ {
field := t.Field(i)
fmt.Printf("Field %d: %s, Tag: %s\n", i, field.Name, field.Tag)
jsonTag := field.Tag.Get("json")
xmlTag := field.Tag.Get("xml")
fmt.Printf("Field %d: %s, JSON Tag: %s, XML Tag: %s\n", i, field.Name, jsonTag, xmlTag)
}
fmt.Println("Nested Struct Tags:")
printStructTags(reflect.TypeOf(p), "")
}
// 递归打印结构体的标签,包括嵌套的结构体
func printStructTags(t reflect.Type, prefix string) {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonTag := field.Tag.Get("json")
xmlTag := field.Tag.Get("xml")
fmt.Printf("%sField: %s, JSON Tag: %s, XML Tag: %s\n", prefix, field.Name, jsonTag, xmlTag)
// 检查嵌套结构体
if field.Type.Kind() == reflect.Struct && field.Anonymous == false {
printStructTags(field.Type, prefix+" ")
}
}
}
/*
Number of fields: 3
Field 0 (Name): Alice
Field 1 (Age): 30
Field Address.City: New York
Field Address.State: NY
Field 'Name': Alice
Field 'Age': 30
Field 'Address': {New York NY}
Field with prefix 'Nam': Alice
*/
/*
Number of fields: 3
Field 0: Name, Tag: json:"name_index" xml:"name_index"
Field 0: Name, JSON Tag: name_index, XML Tag: name_index
Field 1: Age, Tag: json:"age_index" xml:"age_index"
Field 1: Age, JSON Tag: age_index, XML Tag: age_index
Field 2: Address, Tag: json:"address_index" xml:"address_index"
Field 2: Address, JSON Tag: address_index, XML Tag: address_index
Nested Struct Tags:
Field: Name, JSON Tag: name_index, XML Tag: name_index
Field: Age, JSON Tag: age_index, XML Tag: age_index
Field: Address, JSON Tag: address_index, XML Tag: address_index
Field: City, JSON Tag: city, XML Tag: city
Field: State, JSON Tag: state, XML Tag: state
*/
转载自https://halfrost.com/go_reflection/#toc-11
Go 语言是强类型语言,编译时对每个变量的类型信息做强校验,所以每个类型的元信息要用一个结构体描述。再者 Go 的反射也是基于类型的元信息实现的。_type 就是所有类型最原始的元信息。
// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
size uintptr // 类型占用内存大小
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags 标记位,主要用于反射
align uint8 // alignment of variable with this type对齐字节信息
fieldAlign uint8 // alignment of struct field with this type 当前结构字段的对齐字节数
kind uint8 // enumeration for C 基础类型枚举值
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个形参对应对象的类型是否相等
gcdata *byte // garbage collection data
str nameOff // string form 类型名称字符串在二进制文件段中的偏移量
ptrToThis typeOff // type for pointer to this type, may be zero类型元信息指针在二进制文件段中的偏移量
}
- kind,这个字段描述的是如何解析基础类型。在 Go 语言中,基础类型是一个枚举常量,有 26 个基础类型,如下。枚举值通过 kindMask 取出特殊标记位。
const (
kindBool = 1 + iota
kindInt
kindInt8
kindInt16
kindInt32
kindInt64
kindUint
kindUint8
kindUint16
kindUint32
kindUint64
kindUintptr
kindFloat32
kindFloat64
kindComplex64
kindComplex128
kindArray
kindChan
kindFunc
kindInterface
kindMap
kindPtr
kindSlice
kindString
kindStruct
kindUnsafePointer
kindDirectIface = 1 << 5
kindGCProg = 1 << 6
kindMask = (1 << 5) - 1
)
- str 和 ptrToThis,对应的类型是 nameoff 和 typeOff。这两个字段的值是在链接器段合并和符号重定向的时候赋值的。
1. reflect.Type 通用方法
以下这些方法是通用方法,可以适用于任何类型。
// Type 是 Go 类型的表示。
//
// 并非所有方法都适用于所有类型。
// 在调用 kind 具体方法之前,先使用 Kind 方法找出类型的种类。因为调用一个方法如果类型不匹配会导致 panic
//
// Type 类型值是可以比较的,比如用 == 操作符。所以它可以用做 map 的 key
// 如果两个 Type 值代表相同的类型,那么它们一定是相等的。
type Type interface {
// Align 返回该类型在内存中分配时,以字节数为单位的字节数
Align() int
// FieldAlign 返回该类型在结构中作为字段使用时,以字节数为单位的字节数
FieldAlign() int
// Method 这个方法返回类型方法集中的第 i 个方法。
// 如果 i 不在[0, NumMethod()]范围内,就会 panic。
// 对于一个非接口类型 T 或 *T,返回的 Method 的 Type 和 Func。
// fields 字段描述一个函数,它的第一个参数是接收方,而且只有导出的方法可以访问。
// 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。
// 方法是按字典序顺序排列的。
Method(int) Method
// MethodByName 返回类型中带有该名称的方法。
// 方法集和一个表示是否找到该方法的布尔值。
// 对于一个非接口类型 T 或 *T,返回的 Method 的 Type 和 Func。
// fields 字段描述一个函数,其第一个参数是接收方。
// 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。
MethodByName(string) (Method, bool)
// NumMethod 返回使用 Method 可以访问的方法数量。
// 请注意,NumMethod 只在接口类型的调用的时候,会对未导出方法进行计数。
NumMethod() int
// 对于定义的类型,Name 返回其包中的类型名称。
// 对于其他(非定义的)类型,它返回空字符串。
Name() string
// PkgPath 返回一个定义类型的包的路径,也就是导入路径,导入路径是唯一标识包的类型,如 "encoding/base64"。
// 如果类型是预先声明的(string, error)或者没有定义(*T, struct{}, []int,或 A,其中 A 是一个非定义类型的别名),包的路径将是空字符串。
PkgPath() string
// Size 返回存储给定类型的值所需的字节数。它类似于 unsafe.Sizeof.
Size() uintptr
// String 返回该类型的字符串表示。
// 字符串表示法可以使用缩短的包名。
// (例如,使用 base64 而不是 "encoding/base64")并且它并不能保证类型之间是唯一的。如果是为了测试类型标识,应该直接比较类型 Type。
String() string
// Kind 返回该类型的具体种类。
Kind() Kind
// Implements 表示该类型是否实现了接口类型 u。
Implements(u Type) bool
// AssignableTo 表示该类型的值是否可以分配给类型 u。
AssignableTo(u Type) bool
// ConvertibleTo 表示该类型的值是否可转换为 u 类型。
ConvertibleTo(u Type) bool
// Comparable 表示该类型的值是否具有可比性。
Comparable() bool
}
2、reflect.Type 专有方法
以下这些方法是某些类型专有的方法,如果类型不匹配会发生 panic。在不确定类型之前最好先调用 Kind() 方法确定具体类型再调用类型的专有方法。
Kind | Methods applicable |
---|---|
Int* | Bits |
Uint* | Bits |
Float* | Bits |
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 |
专有方法的说明如下:
type Type interface {
// Bits 以 bits 为单位返回类型的大小。
// 如果类型的 Kind 不属于:sized 或者 unsized Int, Uint, Float, 或者 Complex,会 panic。
//大小不一的Int、Uint、Float或Complex类型。
Bits() int
// ChanDir 返回一个通道类型的方向。
// 如果类型的 Kind 不是 Chan,会 panic。
ChanDir() ChanDir
// IsVariadic 表示一个函数类型的最终输入参数是否为一个 "..." 可变参数。如果是,t.In(t.NumIn() - 1) 返回参数的隐式实际类型 []T.
// 更具体的,如果 t 代表 func(x int, y ... float64),那么:
// t.NumIn() == 2
// t.In(0)是 "int" 的 reflect.Type 反射类型。
// t.In(1)是 "[]float64" 的 reflect.Type 反射类型。
// t.IsVariadic() == true
// 如果类型的 Kind 不是 Func.IsVariadic,IsVariadic 会 panic
IsVariadic() bool
// Elem 返回一个 type 的元素类型。
// 如果类型的 Kind 不是 Array、Chan、Map、Ptr 或 Slice,就会 panic
Elem() Type
// Field 返回一个结构类型的第 i 个字段。
// 如果类型的 Kind 不是 Struct,就会 panic。
// 如果 i 不在 [0, NumField()] 范围内,也会 panic。
Field(i int) StructField
// FieldByIndex 返回索引序列对应的嵌套字段。它相当于对每一个 index 调用 Field。
// 如果类型的 Kind 不是 Struct,就会 panic。
FieldByIndex(index []int) StructField
// FieldByName 返回给定名称的结构字段和一个表示是否找到该字段的布尔值。
FieldByName(name string) (StructField, bool)
// FieldByNameFunc 返回一个能满足 match 函数的带有名称的 field 字段。布尔值表示是否找到。
// FieldByNameFunc 先在自己的结构体的字段里面查找,然后在任何嵌入结构中的字段中查找,按广度第一顺序搜索。最终停止在含有一个或多个能满足 match 函数的结构体中。如果在该深度上满足条件的有多个字段,这些字段相互取消,并且 FieldByNameFunc 返回没有匹配。
// 这种行为反映了 Go 在包含嵌入式字段的结构的情况下对名称查找的处理方式
FieldByNameFunc(match func(string) bool) (StructField, bool)
// In 返回函数类型的第 i 个输入参数的类型。
// 如果类型的 Kind 不是 Func 类型会 panic。
// 如果 i 不在 [0, NumIn()) 的范围内,会 panic。
In(i int) Type
// Key 返回一个 map 类型的 key 类型。
// 如果类型的 Kind 不是 Map,会 panic。
Key() Type
// Len 返回一个数组类型的长度。
// 如果类型的 Kind 不是 Array,会 panic。
Len() int
// NumField 返回一个结构类型的字段数目。
// 如果类型的 Kind 不是 Struct,会 panic。
NumField() int
// NumIn 返回一个函数类型的输入参数数。
// 如果类型的 Kind 不是Func.NumIn(),会 panic。
NumIn() int
// NumOut 返回一个函数类型的输出参数数。
// 如果类型的 Kind 不是 Func.NumOut(),会 panic。
NumOut() int
// Out 返回一个函数类型的第 i 个输出参数的类型。
// 如果类型的类型不是 Func.Out,会 panic。
// 如果 i 不在 [0, NumOut()) 的范围内,会 panic。
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
3. reflect.Value 数据结构
在 reflect 包中,并非所有的方法都适用于所有类型的值。具体的限制在方法说明注释里面有写。在调用特定种类的方法之前,最好使用 Kind 方法找出 Value 的种类。和 reflect.Type 一样,调用类型不匹配的方法会导致 panic。需要特殊说明的是 zero Value,zero Value 代表没有值。它的 IsValid() 方法返回 false,Kind() 方法返回 Invalid,String() 方法返回 “”,而剩下的所有其他方法均会 panic。大多数函数和方法从不返回 invalid value。如果确实返回了 invalid value,则其文档会明确说明特殊条件。
reflect 包里的 Value数据结构如下:
type Value struct {
// typ 包含由值表示的值的类型。
typ *rtype
// 指向值的指针,如果设置了 flagIndir,则是指向数据的指针。只有当设置了 flagIndir 或 typ.pointers()为 true 时有效。
ptr unsafe.Pointer
// flag 保存有关该值的元数据。最低位是标志位:
// - flagStickyRO: 通过未导出的未嵌入字段获取,因此为只读
// - flagEmbedRO: 通过未导出的嵌入式字段获取,因此为只读
// - flagIndir: val保存指向数据的指针
// - flagAddr: v.CanAddr 为 true (表示 flagIndir)
// - flagMethod: v 是方法值。
// 接下来的 5 个 bits 给出 Value 的 Kind 种类,除了方法 values 以外,它会重复 typ.Kind()。其余 23 位以上给出方法 values 的方法编号。如果 flag.kind()!= Func,代码可以假定 flagMethod 没有设置。如果 ifaceIndir(typ),代码可以假定设置了 flagIndir。
flag
}
reflect.TypeOf() 底层实现
在 reflect 包中有一个重要的方法 TypeOf(),利用这个方法可以获得一个 Type 的 interface。通过 Type interface 可以获取对象的类型信息。
// TypeOf() 方法返回的 i 这个动态类型的 Type。如果 i 是一个 nil interface value, TypeOf 返回 nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
emptyInterface 数据结构如下:
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
emptyInterface 其实就是 reflect 版的 eface,数据结构完全一致,所以此处强制类型转换没有问题。
TypeOf() 入参,入参类型是 i interface{},可以是 2 种类型,一种是 interface 变量,另外一种是具体的类型变量。如果 i 是具体的类型变量,TypeOf() 返回的具体类型信息;如果 i 是 interface 变量,并且绑定了具体类型对象实例,返回的是 i 绑定具体类型的动态类型信息;如果 i 没有绑定任何具体的类型对象实例,返回的是接口自身的静态类型信息。例如下面这段代码:
import (
"fmt"
"reflect"
)
func main() {
ifa := new(Person)
var ifb Person = Student{name: "halfrost"}
// 未绑定具体变量的接口类型
fmt.Println(reflect.TypeOf(ifa).Elem().Name())
fmt.Println(reflect.TypeOf(ifa).Elem().Kind().String())
// 绑定具体变量的接口类型
fmt.Println(reflect.TypeOf(ifb).Name())
fmt.Println(reflect.TypeOf(ifb).Kind().String())
}
Person
interface
Student
struct
在第一组输出中,reflect.TypeOf() 入参未绑定具体变量的接口类型,所以返回的是接口类型本身 Person。对应的 Kind 是 interface。
在第二组输出中,reflect.TypeOf() 入参绑定了具体变量的接口类型,所以返回的是绑定的具体类型 Student。对应的 Kind 是 struct。
reflect.ValueOf() 底层实现
ValueOf() 方法返回一个新的 Value,根据 interface i 这个入参的具体值进行初始化。ValueOf(nil) 返回零值。
反射三定律
1、反射可以从接口值中得到反射对象
- 通过实例获取 Value 对象,使用 reflect.ValueOf() 函数。
- 通过实例获取反射对象 Type,使用 reflect.TypeOf() 函数。
2. 反射可以从反射对象中获得接口值
- 将 Value 转换成空的 interface,内部存放具体类型实例。使用 interface() 函数。
// Interface returns v's current value as an interface{}.
// It is equivalent to:
// var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}
- Value 也包含很多成员方法,可以将 Value 转换成简单类型实例,注意如果类型不匹配会 panic。
// Int returns v's underlying value, as an int64.
// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64.
func (v Value) Int() int64 {
k := v.kind()
p := v.ptr
switch k {
case Int:
return int64(*(*int)(p))
case Int8:
return int64(*(*int8)(p))
case Int16:
return int64(*(*int16)(p))
case Int32:
return int64(*(*int32)(p))
case Int64:
return *(*int64)(p)
}
panic(&ValueError{"reflect.Value.Int", v.kind()})
}
从反射对象到接口值的过程是从接口值到反射对象的镜面过程,两个过程都需要经历两次转换:
- 从接口值到反射对象:
- 从基本类型到接口类型的类型转换;
- 从接口类型到反射对象的转换;
- 从反射对象到接口值:
- 反射对象转换成接口类型;
- 通过显式类型转换变成原始类型;
3、若要修改反射对象,值必须可修改
- 指针类型 Type 转成值类型 Type。指针类型必须是 *Array、*Slice、*Pointer、*Map、*Chan 类型,否则会发生 panic。Type 返回的是内部元素的 Type。
// Elem returns element type of array a.
func (a *Array) Elem() Type { return a.elem }
// Elem returns the element type of slice s.
func (s *Slice) Elem() Type { return s.elem }
// Elem returns the element type for the given pointer p.
func (p *Pointer) Elem() Type { return p.base }
// Elem returns the element type of map m.
func (m *Map) Elem() Type { return m.elem }
// Elem returns the element type of channel c.
func (c *Chan) Elem() Type { return c.elem }
- 值类型 Type 转成指针类型 Type。PtrTo 返回的是指向 t 的指针类型 Type。
针对反射三定律的这个第三条,还需要特殊说明的是:Value 值的可修改性是什么意思。举例
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
}
崩溃信息是 panic: reflect: reflect.Value.SetFloat using unaddressable value
,为什么这里 SetFloat() 会 panic 呢?这里给的提示信息是使用了不可寻址的 Value。在上述代码中,调用 reflect.ValueOf 传进去的是一个值类型的变量,获得的 Value 其实是完全的值拷贝,这个 Value 是不能被修改的。如果传进去是一个指针,获得的 Value 是一个指针副本,但是这个指针指向的地址的对象是可以改变的。将上述代码改成这样:
nc main() {
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
v := p.Elem()
v.SetFloat(7.1)
fmt.Println(v.Interface()) // 7.1
fmt.Println(x) // 7.1
}
4、Type 和 Value 相互转换
- 由于 Type 中只有类型信息,所以无法直接通过 Type 获取实例对象的 Value,但是可以通过 New() 这个方法得到一个指向 type 类型的指针,值是零值。MakeMap() 方法和 New() 方法类似,只不过是创建了一个 Map。
- 需要特殊说明的一个方法是 Zero(),这个方法返回指定类型的零值。这个零值与 Value 结构的 zero value 不同,它根本不代表任何值。例如,Zero(TypeOf(42)) 返回带有 Kind Int 且值为 0 的值。返回的值既不可寻址,也不可改变。
- 由于反射对象 Value 中本来就存有 Tpye 的信息,所以 Value 向 Type 转换比较简单。
func (v Value) Type() Type {
f := v.flag
if f == 0 {
panic(&ValueError{"reflect.Value.Type", Invalid})
}
if f&flagMethod == 0 {
// Easy case
return v.typ
}
// Method value.
// v.typ describes the receiver, not the method type.
i := int(v.flag) >> flagMethodShift
if v.typ.Kind() == Interface {
// Method on interface.
tt := (*interfaceType)(unsafe.Pointer(v.typ))
if uint(i) >= uint(len(tt.methods)) {
panic("reflect: internal error: invalid method index")
}
m := &tt.methods[i]
return v.typ.typeOff(m.typ)
}
// Method on concrete type.
ms := v.typ.exportedMethods()
if uint(i) >= uint(len(ms)) {
panic("reflect: internal error: invalid method index")
}
m := ms[i]
return v.typ.typeOff(m.mtyp)
}
5. Value 指针转换成值
- 把指针的 Value 转换成值 Value 有 2 个方法 Indirect() 和 Elem()。
- 将值 Value 转换成指针的 Value 只有 Addr() 这一个方法。