简言之:就是利用【反射功能】注册【一些信息】到程序可执行文件中,因此在程序运行期间,可以利用【反射功能】取出或修改【这些信息】
反射是**【指在程序运行期间对程序本身进行访问和修改的能力】**。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期间将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期间获取类型的反射信息,并且有能力修改它们。
需要反射的 2 个常见场景:
在讲反射的原理以及如何用之前,还是说几点不使用反射的理由:
当向【接口变量】赋予一个【实体类型】的时候,【接口会存储实体的类型信息】,反射就是【通过接口的类型信息实现的】,反射建立在类型的基础上。
Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。
package main
import (
"fmt"
"reflect"
)
func reflectType(x interface{}) {
v := reflect.TypeOf(x)
fmt.Printf("type:%v\n", v)
}
func main() {
var a float32 = 3.14
reflectType(a) // type:float32
var b int64 = 100
reflectType(b) // type:int64
}
反射主要与 interface{} 类型相关。下面介绍一下 interface 的底层结构
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32
bad bool
inhash bool
unused [2]byte
fun [1]uintptr
}
其中 itab
由具体类型 _type
以及 interfacetype
组成。_type
表示具体类型,而 interfacetype
则表示具体类型实现的接口类型。
实际上,iface 描述的是非空接口,它包含方法;与之相对的是 eface
,描述的是空接口,不包含任何方法,Go 语言里有的类型都 “实现了”
空接口。
type eface struct {
_type *_type
data unsafe.Pointer
}
相比 iface
,eface
就比较简单了。只维护了一个 _type
字段,表示空接口所承载的具体的实体类型。data
描述了具体的值。
ValueOf
函数返回值reflect.Type
主要提供关于类型相关的信息,所以它和_type
关联比较紧密;
ValueOf
函数返回值reflect.Value
表示interface{}
里存储的实际变量,通过Type()
方法和Interface()
方法可以打通interface
、Type
、Value
三者。
Type()
方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。
Interface()
方法可以将 Value 还原成原来的 interface。
reflect 包里定义了一个接口和一个结构体,即 reflect.Type
和 reflect.Value
,它们提供很多函数来获取存储在接口里的类型信息。
reflect.Type
主要提供关于类型相关的信息,所以它和 _type
关联比较紧密;reflect.Value
则结合 _type
和 data
两者,因此程序员可以获取甚至改变类型的值。reflect 包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
TypeOf
函数用来提取一个接口中值的类型信息。由于它的输入参数是一个空的 interface{}
,调用此函数时,实参会先被转化为 interface{}
类型。这样,实参的类型信息、方法集、值信息都存储到 interface{}
变量里了。
看下源码:
// 这里的 `emptyInterface` 和上面提到的 `eface` 是一回事(字段名略有差异,字段是相同的),
// 且在不同的源码包:前者在 `reflect` 包,后者在 `runtime` 包。 `eface.typ` 就是动态类型。
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// 至于 `toType` 函数,只是做了一个类型转换:
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
// 注意,返回值 `Type` 实际上是一个接口,定义了很多方法,用来获取类型相关的各种信息,而 `*rtype` 实现了 `Type` 接口。
// Type 定义了非常多的方法,通过它们可以获取类型的一切信息,大家一定要完整的过一遍下面所有的方法。
type Type interface {
// 所有的类型都可以调用下面这些函数
// 此类型的变量对齐后所占用的字节数
Align() int
// 如果是 struct 的字段,对齐后占用的字节数
FieldAlign() int
// 返回类型方法集里的第 `i` (传入的参数)个方法
Method(int) Method
// 通过名称获取方法
MethodByName(string) (Method, bool)
// 获取类型方法集里导出的方法个数
NumMethod() int
// 类型名称
Name() string
// 返回类型所在的路径,如:encoding/base64
PkgPath() string
// 返回类型的大小,和 unsafe.Sizeof 功能类似
Size() uintptr
// 返回类型的字符串表示形式
String() string
// 返回类型的类型值 —— 底层类型
Kind() Kind
// 类型是否实现了接口 u
Implements(u Type) bool
// 是否可以赋值给 u
AssignableTo(u Type) bool
// 是否可以类型转换成 u
ConvertibleTo(u Type) bool
// 类型是否可以比较
Comparable() bool
// 下面这些函数只有特定类型可以调用
// 如:Key, Elem 两个方法就只能是 Map 类型才能调用
// 类型所占据的位数
Bits() int
// 返回通道的方向,只能是 chan 类型调用
ChanDir() ChanDir
// 返回类型是否是可变参数,只能是 func 类型调用
// 比如 t 是类型 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool
// 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
// 这个很常用,比如变量 test 原类型为 []int , reflect.TypeOf(test).Elem() 会返回 int(即为子元素的类型)
Elem() Type
// 返回结构体类型的第 i 个字段,只能是结构体类型调用
// 如果 i 超过了总字段数,就会 panic
Field(i int) StructField
// 返回嵌套的结构体的字段
FieldByIndex(index []int) StructField
// 通过字段名称获取字段
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// 返回名称符合 func 函数的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 获取函数类型的第 i 个参数的类型
In(i int) Type
// 返回 map 的 key 类型,只能由类型 map 调用
Key() Type
// 返回 Array 的长度,只能由类型 Array 调用
Len() int
// 返回类型字段的数量,只能由类型 Struct 调用
NumField() int
// 返回函数类型的输入参数个数
NumIn() int
// 返回函数类型的返回值个数
NumOut() int
// 返回函数类型的第 i 个值的类型
Out(i int) Type
// 返回类型结构体的相同部分
common() *rtype
// 返回类型结构体的不同部分
uncommon() *uncommonType
}
讲完了 TypeOf
函数,再来看一下 ValueOf
函数。返回值 reflect.Value
表示 interface{}
里存储的实际变量,它能提供实际变量的各种信息。相关的方法常常是需要结合类型信息和值信息。例如,如果要提取一个结构体的字段信息,那就需要用到 _type (具体到这里是指 structType) 类型持有的关于结构体的字段信息、偏移信息,以及 *data
所指向的内容 —— 结构体的实际值。
源码如下:
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// ……
return unpackEface(i)
}
// 分解 eface
// 从源码看,比较简单:将先将 `i` 转换成 `*emptyInterface` 类型,
// 再将它的 `typ` 字段和 `word` 字段以及一个标志位字段组装成一个 `Value` 结构体,而这就是 `ValueOf` 函数的返回值,
// 它包含类型结构体指针、真实数据的地址、标志位。
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
Value 结构体定义了很多方法,通过这些方法可以直接操作 Value 字段 ptr 所指向的实际数据:
// 设置切片的 len 字段,如果类型不是切片,就会panic
func (v Value) SetLen(n int)
// 设置切片的 cap 字段
func (v Value) SetCap(n int)
// 设置字典的 kv
func (v Value) SetMapIndex(key, val Value)
// 返回切片、字符串、数组的索引 i 处的值
func (v Value) Index(i int) Value
// 根据名称获取结构体的内部字段值
func (v Value) FieldByName(name string) Value
// ……
Value
字段还有很多其他的方法。例如:
// 用来获取 int 类型的值
func (v Value) Int() int64
// 用来获取结构体字段(成员)数量
func (v Value) NumField() int
// 尝试向通道发送数据(不会阻塞)
func (v Value) TrySend(x reflect.Value) bool
// 通过参数列表 in 调用 v 值所代表的函数(或方法
func (v Value) Call(in []Value) (r []Value)
// 调用变参长度可变的函数
func (v Value) CallSlice(in []Value) []Value
不一一列举了,反正是非常多。可以去 src/reflect/value.go
去看看源码,搜索 func (v Value)
就能看到。
另外,通过
Type()
方法和Interface()
方法可以打通interface
、Type
、Value
三者。
Type()
方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface()
方法可以将 Value 还原成原来的 interface。- 见下图
golang 反射不能获取和修改 私有(小写字母)的属性以及方法
ValueOf(*ptr) 方法传递的参数必须是 指针类型 才可以修改字段否则会报错
reflect.Type 类型是一个接口类型,内部指定了若干方法,通过这些方法我们可以获取到反射类型的各种信息,例如:字段、方法等
使用 *reflect.TypeOf()* 函数可以获取将任意值的类型对象 (reflect.Type
),程序通过类型对象可以访问任意值的类型信息
reflect.Type 是变量的类型,而不是追根溯源的最底层类型
type MyInt int
reflect.TypeOf(MyInt).Kind()
这里的 reflect.Type 就是 MyInt
,而非 int,如果想获得 int 只能使用Kind()
总结:*Type 表示的是静态类型,而 kind 表示的是底层真实的类型*
package main
import (
"fmt"
"reflect"
)
// 定义一个 MyInt 类型
type MyInt int
func main() {
// 声明一个空结构体
type cat struct {
}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(cat{})
// 显示反射类型对象的名称和种类
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
// 获取Zero常量的反射类型对象
typeOfA := reflect.TypeOf(Zero)
// 显示反射类型对象的名称和种类
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
代码输出如下:
cat struct
MyInt int
获取与成员相关的方法如下:
方法 | 描述 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机 |
NumField() int | 返回结构体成员字段数量(包含私有字段) |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值 |
StructField结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
Name string
// 带有结构体tag的字段
Type int `json:"type" id:"100"`
}
// 创建cat的实例
ins := cat{Name: "mimi", Type: 1}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 遍历结构体所有成员
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取每个成员的结构体字段类型
fieldType := typeOfCat.Field(i)
// 输出成员名和tag
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)
}
// 通过字段名, 找到字段类型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 从tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针
func main() {
var a int
// 取变量a的反射类型对象
typeOfA := reflect.TypeOf(a)
// 根据反射类型对象创建类型实例
aIns := reflect.New(typeOfA)
// 输出:*int ptr
fmt.Println(aIns.Type(), aIns.Kind())
}
reflect.Value 类型是一个结构体,封装了反射对象的值,内部若干方法,可以通过这些方法来获取和修改对象的值,使用 reflect.ValueOf
函数可以返回 Value 类型,value 类型还可以生成原始类型对象
可以通过下面几种方法从反射值对象 reflect.Value 中获取原值
方法名 | 说 明 |
---|---|
Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
Bool() bool | 将值以 bool 类型返回 |
Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
String() string | 将值以字符串类型返回 |
代码演示如下👇
func main() {
// 声明整型变量a并赋初值
var a int = 1024
// 获取变量a的反射值对象
valueOfA := reflect.ValueOf(a)
// 获取interface{}类型的值, 通过类型断言转换
var getA int = valueOfA.Interface().(int)
// 获取64位的值, 强制类型转换为int类型
var getA2 int = int(valueOfA.Int())
fmt.Println(getA, getA2)
}
反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,方法列表参考 Type 常用方法
修改成员的值 使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。如果该对象不可寻址或者成员是私有的,则无法修改对象值
判定是否可以操作的方法有如下👇
方法名 | 描 述 |
---|---|
Elem() Value | 取值指向的元素值,类似于语言层* 操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value |
Addr() Value | 对可寻址的值返回其地址,类似于语言层& 操作。当值不可寻址时发生宕机 |
CanAddr() bool | 表示值是否可寻址 |
CanSet() bool | 返回值能否被修改。要求值可寻址且是导出的字段 |
修改的方法如下👇
Set(x Value) | 将值设置为传入的反射值对象的值 |
---|---|
Setlnt(x int64) | 使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机 |
SetUint(x uint64) | 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机 |
SetFloat(x float64) | 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机 |
SetBool(x bool) | 使用 bool 设置值。当值的类型不是 bod 时会发生宕机 |
SetBytes(x []byte) | 设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机 |
SetString(x string) | 设置字符串值。当值的类型不是 string 时会发生宕机 |
代码案例如下
func main() {
type dog struct {
LegCount int
age int
}
// 获取dog实例地址的反射值对象
valueOfDog := reflect.ValueOf(&dog{})
// 取出dog实例地址的元素
valueOfDog = valueOfDog.Elem()
// 获取legCount字段的值
vLegCount := valueOfDog.FieldByName("LegCount")
vAge := valueOfDog.FieldByName("age")
// 尝试设置legCount的值
vLegCount.SetInt(4)
// 这里会报错
vAge.SetInt(4)
fmt.Println(vLegCount.Int())
}
如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数,使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回
package main
import (
"fmt"
"reflect"
)
// 普通函数
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())
}
如果反射值对象中具有方法时,可以通过反射调用方法,获取方法如下👇
方法 | 描述 |
---|---|
Method(i int) Value | 根据索引,返回索引对应的方法 |
NumMethod() int | 返回结构体成员方法(包含私有) |
MethodByName(name string) Value | 根据给定字符串返回字符串对应的结构体方法 |
package main
import (
"fmt"
"reflect"
)
type Cat struct {
Name string
}
func (c *Cat) Sleep() {
fmt.Println("呜呜呜...")
}
func main() {
cat := Cat{}
valueOf := reflect.ValueOf(&cat)
showMethod := valueOf.MethodByName("Show")
showMethod.Call(nil)
}
func Map2Struct(m map[string]interface{}, obj interface{}) {
value := reflect.ValueOf(obj)
// obj 必须是指针且指针指向的必须是 struct
if value.Kind() == reflect.Ptr && value.Elem().Kind() == reflect.Struct {
value = value.Elem()
getMapName := func(key string) interface{} {
for k, v := range m {
if strings.EqualFold(k, key) {
return v
}
}
return nil
}
// 循环赋值
for i := 0; i < value.NumField(); i++ {
// 获取字段 type 对象
field := value.Field(i)
if !field.CanSet() {
continue
}
// 获取字段名称
fieldName := value.Type().Field(i).Name
fmt.Println("fieldName -> ", fieldName)
// 获取 map 中的对应的值
fieldVal := getMapName(fieldName)
if fieldVal != nil {
field.Set(reflect.ValueOf(fieldVal))
}
}
} else {
panic("must prt")
}
}
func Struct2Map(obj interface{}) map[string]interface{} {
value := reflect.ValueOf(obj)
if value.Kind() != reflect.Ptr || value.Elem().Kind() != reflect.Struct {
panic("must prt")
}
value = value.Elem()
t := value.Type()
// 创建 map
resultMap := make(map[string]interface{})
// 循环获取字段名称以及对应的值
for i := 0; i < value.NumField(); i++ {
val := value.Field(i)
typeName := t.Field(i)
if !val.CanSet() {
resultMap[typeName.Name] = reflect.New(typeName.Type).Elem().Interface()
continue
}
resultMap[typeName.Name] = val.Interface()
}
return resultMap
}