跳至主要內容

golang基础 - 反射机制

Zenghr大约 8 分钟golanggolang

golang 反射机制

和 Java 语言一样,Go 也实现运行时反射,这为我们提供一种可以在运行时操作任意类型对象的能力。

在 go 语言中,实现反射能力的是 reflect包,能够让程序操作不同类型的对象。其中,在反射包中有两个非常重要的 类型函数,两个函数分别是:

  • reflect.TypeOf - 能获取对象的类型的信息
  • reflect.ValueOf - 能获取对象的数据

两个类型是 reflect.Typeopen in new windowreflect.Valueopen in new window,它们与函数是一一对应的关系:

6hzQxX
6hzQxX

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
}