1.reflect包作用
reflect包定义了“反射”相关能力,“反射”在计算机学中是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。基于反射特性可以通用化地解决一些需要频繁修改代码及硬编码问题,但是执行效率会被降低。
2.核心API
参考官方文档:https://pkg.go.dev/reflect#pkg-functions
3.使用案例
3.1.类型判断
- 用途:判断接口属于某种类型后进行特定处理
- 主要使用:TypeOf、Kind
type User struct {
Name string
Age int
}
type IUserService interface {
GetUser(userID int64) User
}
func reflectType() {
var (
intType int
stringType *string
mapType map[string]interface{
}
structType *User
interfaceType IUserService
)
var types []interface{
}
types = append(types, intType)
types = append(types, stringType)
types = append(types, mapType)
types = append(types, structType)
types = append(types, interfaceType)
// 类型判断
for i, v := range types {
rtyp := reflect.TypeOf(v)
if rtyp == nil {
fmt.Printf("(%v)类型获取Type为nil,跳过\n", i)
continue
}
rkind := rtyp.Kind()
// 如果为指针类型,还原出真实类型
if rtyp.Kind() == reflect.Ptr {
fmt.Printf("(%v)指针类型: %v\n", i, rkind.String())
rtyp = rtyp.Elem()
}
rkind = rtyp.Kind()
fmt.Printf("(%v)类型%v\n", i, rkind.String())
}
}
输出:
(0)类型int
(1)指针类型: ptr
(1)类型string
(2)类型map
(3)指针类型: ptr
(3)类型struct
(4)类型获取Type为nil,跳过
3.2.变量值获取
- 用于:获取类型的值
- 主要使用:ValueOf、Kind
func reflectValue() {
var (
intType int = 10
stringType string = "hello world"
mapType map[string]interface{
} = nil
)
var types []interface{
}
types = append(types, intType)
types = append(types, stringType)
types = append(types, mapType)
// 类型判断
for i, v := range types {
rval := reflect.ValueOf(v)
if rval.Kind() == reflect.Ptr {
rval = rval.Elem()
}
fmt.Printf("(%v)的值%v\n", i, rval.Interface())
}
}
输出:
(0)的值10
(1)的值hello world
(2)的值map[]
3.3.遍历结构体的字段及值
- 用途:遍历未知类型结构体的字段及值,避免硬编码结构体处理
- 主要使用:TypeOf、ValueOf、NumField
func rangeStruct() {
u := User{
Name: "arong",
Age: 23,
Favors: []Favor{
{
Name: "篮球",
ID: 1,
},
{
Name: "唱跳",
ID: 2,
},
{
Name: "RAP",
ID: 3,
},
},
}
rval := reflect.ValueOf(&u)
if rval.Kind() == reflect.Ptr {
rval = rval.Elem()
}
for i := 0; i < rval.NumField(); i++ {
name := rval.Type().Field(i).Name
val := rval.Field(i).Interface()
fmt.Printf("字段%v值为%v\n", name, val)
}
}
输出:
字段Name值为arong
字段Age值为23
字段Favors值为[{篮球 1} {唱跳 2} {RAP 3}]
3.4.结构体字段赋值
- 用途:对未知结构体的特定字段进行赋值
- 主要使用:ValueOf、FieldByName、CanSet、Set
func reflectSetValue() {
u := User{
Name: "arong",
Age: 23,
}
fmt.Printf("原始字段值:%#v\n", u)
// 一定要取指针,不然无法赋值
rval := reflect.ValueOf(&u).Elem()
rvalNameField := rval.FieldByName("Name")
// 字段是否可写入
if rvalNameField.CanSet() {
rvalNameField.Set(reflect.ValueOf("pbrong"))
}
fmt.Printf("改变已知字段值:%#v\n", u)
}
3.5.函数及方法调用
- 用途:使用反射动态调用指定函数及方法
- 主要使用:
type User struct {
Name string
Age int
Favors []Favor
}
type IUserService interface {
GetUser(userID int64) User
}
type UserService struct {
}
func (u *UserService) GetUser(userID int64) User {
user := User{
Name: "defaultTestUser",
Age: -1,
Favors: []Favor{
},
}
fmt.Printf("GetUser exec: result = %#v\n", user)
return user
}
func TestFunc(names string) {
fmt.Printf("TestFunc exec: result = %v\n", names)
}
func reflectSetValue() {
u := User{
Name: "arong",
Age: 23,
}
fmt.Printf("原始字段值:%#v\n", u)
// 一定要取指针,不然无法赋值
rval := reflect.ValueOf(&u).Elem()
rvalNameField := rval.FieldByName("Name")
// 字段是否可写入
if rvalNameField.CanSet() {
rvalNameField.Set(reflect.ValueOf("pbrong"))
}
fmt.Printf("改变已知字段值:%#v\n", u)
}
输出:
TestFunc exec: result = pbrong
GetUser exec: result = main.User{Name:"defaultTestUser", Age:-1, Favors:[]main.Favor{}}
GetUser resp: main.User{Name:"defaultTestUser", Age:-1, Favors:[]main.Favor{}}
4.避免滥用反射
反射使用不当可能会影响程序的性能和可读性。因此,在 Go 中,我们通常建议尽可能地避免滥用反射。
主要的原因是,反射的本质是一种运行时类型转换,会导致一定的开销。因为它需要动态地获取类型信息,进行类型检查,以及在运行时动态地分配内存等。这些操作都比静态类型转换需要更多的计算资源和时间。
另外,由于反射的使用不够直观和简洁,可能会降低代码的可读性和可维护性。特别是当开发人员不了解反射的工作原理时,容易出现一些难以调试和排查的问题。
因此,通常情况下我们应该尽可能地避免滥用反射。只有在必要的场景下才使用反射,例如需要在运行时动态地创建对象、调用函数、解析数据等。在其他场景下,我们应该尽可能使用 Go 的静态类型系统和语言特性,使得代码更加简洁、高效和可读。