golang基础 - 反射机制
golang 反射机制
和 Java 语言一样,Go 也实现运行时反射,这为我们提供一种可以在运行时操作任意类型对象的能力。
在 go 语言中,实现反射能力的是 reflect
包,能够让程序操作不同类型的对象。其中,在反射包中有两个非常重要的 类型和 函数,两个函数分别是:
reflect.TypeOf
- 能获取对象的类型的信息reflect.ValueOf
- 能获取对象的数据
两个类型是 reflect.Type
和 reflect.Value
,它们与函数是一一对应的关系:
golang 反射注意
提示
golang 反射不能获取和修改 私有的属性以及方法
*ValueOf(ptr) 方法传递的参数必须是 指针类型 才可以修改字段否则会报错
Type 和 TypeOf
reflect.Type 类型是一个接口类型,内部指定了若干方法,通过这些方法我们可以获取到反射类型的各种信息,例如:字段、方法等
使用 reflect.TypeOf() 函数可以获取将任意值的类型对象 (reflect.Type
),程序通过类型对象可以访问任意值的类型信息
1. 理解 Type 和 种类 Kind
reflect.Type 是变量的类型,而不是追根溯源的最底层类型
type MyInt int
reflect.TypeOf(MyInt).Kind()
这里的 reflect.Type 就是 MyInt
,而非 int,如果想获得 int 只能使用Kind()
总结:Type 表示的是静态类型,而 kind 表示的是底层真实的类型
2. 获取类型名以及 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
3. Type 常用方法
获取与成员相关的方法如下:
方法 | 描述 |
---|---|
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 // 是否为匿名字段
}
4. 获取成员反射信息
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"))
}
}
5. 通过类型信息创建实例
当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针
func main() {
var a int
// 取变量a的反射类型对象
typeOfA := reflect.TypeOf(a)
// 根据反射类型对象创建类型实例
aIns := reflect.New(typeOfA)
// 输出:*int ptr
fmt.Println(aIns.Type(), aIns.Kind())
}
Value 和 ValueOf
reflect.Value 类型是一个结构体,封装了反射对象的值,内部若干方法,可以通过这些方法来获取和修改对象的值,使用 reflect.ValueOf
函数可以返回 Value 类型,value 类型还可以生成原始类型对象
1. 生成原始类型的对象
可以通过下面几种方法从反射值对象 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)
}
2. 操作结构体成员的值
反射值对象(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)
}
反射实现:map 转 struct
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")
}
}
反射实现:struct 转 map
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
}