Reflect #
reflect包是Go语言标准库中的一个包,它提供了一组功能,允许我们在运行时动态地查看和操作Go程序中的变量、函数和类型。通过使用reflect包,我们可以以一种通用的方式处理和操作各种类型的值,而无需知道它们的具体类型。
反射三大定律 #
Go 语言中的反射,其归根究底都是在实现三大定律:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
我们将针对这核心的三大定律进行介绍和说明,以此来理解 Go 反射里的各种方法是基于什么理念实现的。
第一定律 #
反射的第一定律是:“反射可以从接口值(interface)得到反射对象”。
示例代码:
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}
输出结果:
type: float64
可能有读者就迷糊了,我明明在代码中传入的变量 x
,他的类型是 float64
。怎么就成从接口值得到反射对象了。
其实不然,虽然在代码中我们所传入的变量基本类型是 float64
,但是 reflect.TypeOf
方法入参是 interface{}
,本质上 Go 语言内部对其是做了类型转换的。这一块会在后面会进一步展开说明。
第二定律 #
反射的第二定律是:“可以从反射对象得到接口值(interface)”。其与第一条定律是相反的定律,可以是互相补充了。
示例代码:
func main() {
vo := reflect.ValueOf(3.4)
vf := vo.Interface().(float64)
log.Println("value:", vf)
}
输出结果:
value: 3.4
可以看到在示例代码中,变量 vo
已经是反射对象,然后我们可以利用其所提供的的 Interface
方法获取到接口值(interface),并最后强制转换回我们原始的变量类型。
第三定律 #
反射的第三定律是:“要修改反射对象,该值必须可以修改”。第三条定律看上去与第一、第二条均无直接关联,但却是必不可少的,因为反射在工程实践中,目的一就是可以获取到值和类型,其二就是要能够修改他的值。
否则反射出来只能看,不能动,就会造成这个反射很鸡肋。例如:应用程序中的配置热更新,必然会涉及配置项相关的变量变动,大多会使用到反射来变动初始值。
示例代码:
func main() {
i := 2.33
v := reflect.ValueOf(&i)
v.Elem().SetFloat(6.66)
log.Println("value: ", i)
}
输出结果:
value: 6.66
单从结果来看,变量 i
的值确实从 2.33
变成了 6.66
,似乎非常完美。
但是单看代码,似乎有些 “问题”,怎么设置一个反射值这么 ”麻烦“:
- 为什么必须传入变量
i
的指针引用? - 为什么变量
v
在设置前还需要Elem
一下?
本叛逆的 Gophper 表示我就不这么设置,行不行呢,会不会出现什么问题:
func main() {
i := 2.33
reflect.ValueOf(i).SetFloat(6.66)
log.Println("value: ", i)
}
报错信息:
panic: reflect: reflect.Value.SetFloat using unaddressable value
goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x8e)
/usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:259 +0x138
reflect.flag.mustBeAssignable(...)
/usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:246
reflect.Value.SetFloat(0x10b2980, 0xc00001a0b0, 0x8e, 0x401aa3d70a3d70a4)
/usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:1609 +0x37
main.main()
/Users/eddycjy/go-application/awesomeProject/main.go:10 +0xc5
根据上述提示可知,由于使用 “使用不可寻址的值”,因此示例程序无法正常的运作下去。并且这是一个 reflect
标准库本身就加以防范了的硬性要求。
这么做的原因在于,Go 语言的函数调用的传递都是值拷贝的,因此若不传指针引用,单纯值传递,那么肯定是无法变动反射对象的源值的。因此 Go 标准库就对其进行了逻辑判断,避免出现问题。
因此期望变更反射对象的源值时,我们必须主动传入对应变量的指针引用,并且调用 reflect
标准库的 Elem
方法来获取指针所指向的源变量,并且最后调用 Set
相关方法来进行设置。
函数 #
New()创建指向指定类型的指针 #
reflect.New
函数用于创建一个新的指向指定类型的指针。它返回一个 reflect.Value
,这个值持有一个指向新分配的零值的指针。
func main() {
varType := reflect.TypeOf(0) // 例如,创建一个 int 类型的指针
newValue := reflect.New(varType)
// 获取指针指向的值
originalValue := newValue.Elem()
fmt.Println("Type:", newValue.Type()) // *int
fmt.Println("Original Value:", originalValue) // 0
// 修改指针指向的值
originalValue.SetInt(42)
fmt.Println("Updated Value:", newValue.Elem()) // 42
}
type Type #
TypeOf()
回任何变量的类型
#
TypeOf()
和ValueOf()
函数是reflect包中最常用的两个函数之一。TypeOf()
函数可以返回任何变量的类型,而ValueOf()
函数可以返回变量的值。这两个函数都接受一个interface{}类型的参数,并返回一个reflect.Type
和reflect.Value
类型的结果。
lessCopy codevar x int = 42
fmt.Println(reflect.TypeOf(x)) // 输出:int
fmt.Println(reflect.ValueOf(x)) // 输出:42
PtoTo() 返回一个表示指向指定类型的指针 #
返回一个reflect.Type
表示指向指定类型的指针。
reflect.PtrTo
以下是使用获取reflect.Type
指向结构类型的指针的示例Person
:
type Person struct {
Name string
Age int
}
func main() {
personType := reflect.TypeOf(Person{})
pointerType := reflect.PtrTo(personType)
fmt.Printf("%v\n", pointerType)
}
在上面的代码片段中,personType
是一个reflect.Type
表示Person
结构类型的值。然后我们使用reflect.PtrTo
to 获取reflect.Type
表示指向结构类型的指针的值Person
,并将结果存储在pointerType
. 最后,我们打印pointerType
值。
该程序的输出将是:
*main.Person
如我们所见,reflect.PtrTo
返回了一个reflect.Type
值,表示指向结构类型的指针Person
。
SliceOf() #
它返回一个reflect.Type
代表指定元素类型的切片。
ValueOf() 返回变量的值 #
reflect.ValueOf()
获取数据信息,返回 Value 类型。
Type()获取 Value
的类型信息
#
Type()
函数在 Go 的反射中用于获取 reflect.Value
的类型信息。
用法
- 获取类型:返回一个
reflect.Type
,表示该值的类型。 - 常用场景:检查变量的类型,比较类型是否匹配。
func main() {
var age int = 30
v := reflect.ValueOf(age)
// 获取类型
t := v.Type()
fmt.Println("Type:", t)
// 检查类型
if t.Kind() == reflect.Int {
fmt.Println("The type is int")
}
}
Elem() 获取其指向的值的反射值 #
使用 Elem()
方法获取其指向的值的反射值。例如,如果 valueOf
是一个指向结构体类型的指针的反射值,那么可以使用以下代码获取其指向的结构体值的反射值:
structValue := valueOf.Elem()
Elem()
方法只能用于指针类型的反射值,否则会抛出一个 panic。如果要获取其他类型的值的成员或字段,需要使用 FieldByName()
或 MethodByName()
等其他反射方法。另外,如果指针的值为 nil
,则 Elem()
方法也会抛出一个 panic。因此,在使用 Elem()
方法时,需要先使用 IsNil()
方法检查指针是否为 nil
。
if !valueOf.IsNil() {
structValue := valueOf.Elem()
// ...
}
if valueOf.Kind() == reflect.Ptr {
valueOf = valueOf.Elem()
}
CanSet()值是否可以被设置 #
CanSet()
方法用于检查一个 reflect.Value
是否可以被设置。只有当该值是可以寻址的(例如,通过指针访问)时,它才返回 true
。
Set()设置值 #
在 Go 中,reflect.Value
的 Set
方法用于设置一个值。要使用 Set
方法,值必须是可设置的(可寻址)。
func main() {
// 定义一个整数变量
x := 10
// 获取变量的反射值
value := reflect.ValueOf(&x).Elem()
// 使用 Set 方法设置新值
if value.CanSet() {
value.SetInt(42)
}
fmt.Println("Updated x:", x) // 输出: Updated x: 42
}
Kind() 获取该反射值的类型 #
Kind()
方法获取该反射值的类型。
func (v Value) Kind() Kind
valueOf := reflect.ValueOf(data) //获取数据
if valueOf.Kind() == reflect.String {
// valueOf 是字符串类型的反射值
}
Interface() 将其转换为对应的接口类型值 #
可以使用 Interface()
方法将其转换为对应的接口类型值。
在将反射值转换为接口类型值时需要使用断言操作符 .(interface{})
显式地将其转换为 interface{}
类型。
if implStruct, ok := valueOf.Interface().(model.TypeCheckSelf); ok {
implStruct.TypeCheckSelf()
}
IsZero() 检查该反射值是否为零值 #
IsZero()
方法检查该反射值是否为零值。
IsZero()
方法则返回一个布尔值,表示该反射值是否为其类型的零值。对于大部分类型来说,零值就是其类型的默认值,例如 int
类型的零值是 0
,string
类型的零值是空字符串 ""
。
func (v Value) IsZero() bool
valueOf := reflect.ValueOf(data) //获取数据
if valueOf.IsZero() { //判断是否为其类型的零值
panic("WriteData data is zero")
}
Indirect(v) 获取v指向的值 #
reflect.Indirect(v)
函数用于获取v指向的值,即,如果v是nil指针,则Indirect返回零值。如果v不是指针,则Indirect返回v。
func main() {
val1:= []int {1, 2, 3, 4}
var val2 reflect.Value = reflect.ValueOf(&val1)
fmt.Println("&val2 type:", val2.Kind())
// using the function
indirectI:= reflect.Indirect(val2)
fmt.Println("indirectI type:", indirectI.Kind())
fmt.Println("indirectI value:", indirectI)
}
&val2 type: ptr
indirectI type: slice
indirectI value: [1 2 3 4]
Index() 获取其指定索引位置的元素的反射值 #
valueOf
是一个切片、数组或字符串类型的反射值,可以使用 Index()
方法获取其指定索引位置的元素的反射值。
需要注意的是,Index()
方法只能用于切片、数组或字符串类型的反射值,否则会抛出一个 panic。如果要获取其他类型的值的成员或字段,需要使用 FieldByName()
或 MethodByName()
等其他反射方法。另外,如果指定的索引超出了切片或数组的长度,或者字符串中没有对应的 Unicode 码点,则 Index()
方法也会抛出一个 panic。因此,在使用 Index()
方法时,需要先使用 Len()
方法获取切片、数组或字符串的长度,然后确保指定的索引不超出范围。
func (v Value) Index(i int) Value
valueOf := reflect.ValueOf(data) //获取数据
if valueOf.Kind() == reflect.Slice {
for i := 0; i < valueOf.Len(); i++ {
fmt.Println(valueOf.Index(i))
}
}
FieldByName(name string)通过字段名称获取结构体中的字段值 #
FieldByName
方法用于通过字段名称获取结构体中的字段值。其签名如下:
func (v Value) FieldByName(name string) Value
- 参数
name
:字段的名称,类型是string
。
- 返回值
- 返回具有指定名称的字段的值,如果字段不存在,则返回一个无效的
reflect.Value
。
- 返回具有指定名称的字段的值,如果字段不存在,则返回一个无效的
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
v := reflect.ValueOf(p)
nameField := v.FieldByName("Name")
if nameField.IsValid() {
fmt.Println("Name:", nameField.String())
} else {
fmt.Println("Name field not found")
}
ageField := v.FieldByName("Age")
if ageField.IsValid() {
fmt.Println("Age:", ageField.Int())
} else {
fmt.Println("Age field not found")
}
}
FieldByNameFunc
通过一个匹配函数获取结构体中的字段值
#
FieldByNameFunc
方法用于通过一个匹配函数获取结构体中的字段值。其签名如下:
func (v Value) FieldByNameFunc(match func(string) bool) Value
- 参数
match
:一个函数,接受一个字符串(字段名)并返回一个布尔值。用于匹配字段名。
- 返回值
- 返回第一个与
match
函数匹配的字段的值,如果没有匹配的字段,则返回一个无效的reflect.Value
。
- 返回第一个与
type Person struct {
Name string
Age int
Location string
}
func main() {
p := Person{Name: "Alice", Age: 30, Location: "New York"}
v := reflect.ValueOf(p)
// 匹配以 "Loc" 开头的字段
field := v.FieldByNameFunc(func(name string) bool {
return strings.HasPrefix(name, "Loc")
})
if field.IsValid() {
fmt.Println("Field found:", field.String())
} else {
fmt.Println("No matching field found")
}
}
IsValid()检查 reflect.Value
是否有效
#
IsValid()
函数在 Go 的反射中用于检查 reflect.Value
是否有效。以下是其主要用途:
- 无效值:如果
reflect.Value
是无效的,通常表示反射操作失败,比如访问了不存在的字段。 - 返回值:
IsValid()
返回false
表示该值无效,true
表示有效。
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
v := reflect.ValueOf(p)
// 尝试获取存在的字段
nameField := v.FieldByName("Name")
if nameField.IsValid() {
fmt.Println("Name field is valid")
} else {
fmt.Println("Name field is invalid")
}
// 尝试获取不存在的字段
invalidField := v.FieldByName("NonExistentField")
if invalidField.IsValid() {
fmt.Println("NonExistentField is valid")
} else {
fmt.Println("NonExistentField is invalid")
}
}
业务实例 #
获取Size字段大小 #
for _, data := range datas {
v := reflect.ValueOf(data)
if v.Kind() != reflect.Struct {
common.Log.Errorf("expected a struct, but got %s", v.Kind())
}
// 获取字段值
fieldVal := v.FieldByName("Size")
if !fieldVal.IsValid() {
fieldVal = v.FieldByName("FileSize")
if !fieldVal.IsValid() {
continue
}
}
totalSize += fieldVal.Int()
}
通过结构体interface类型,得到结构体,并查询数据库 #
func (dp *DataProxy) DeleteDatas(dataType string, cid, eid, nid int64) (err error) {
data, found := vmodel.DataType[dataType]
if !found {
err = fmt.Errorf("data type not found: %s", dataType)
return
}
tableName := db.GetDataTableName(eid, dataType)
engine, found, errTemp := db.GetDBInstance().GetDataEngine(cid, eid, dataType, false)
if !found {
return
}
if errTemp != nil {
err = errTemp
return
}
if !found {
err = fmt.Errorf("table not found: %s", tableName)
return
}
vp := reflect.New(reflect.TypeOf(data))
var v reflect.Value
if vp.Kind() == reflect.Ptr {
v = vp.Elem()
}
field := v.FieldByName("Pid")
if !field.IsValid() {
return fmt.Errorf("no such field: %s in struct")
}
if !field.CanSet() {
return fmt.Errorf("cannot set field %s")
}
val := reflect.ValueOf(nid)
if field.Type() != val.Type() {
return fmt.Errorf("provided value type doesn't match field type")
}
field.Set(val)
err = engine.DeleteByStruct(tableName, vp.Interface())
if err != nil {
err = fmt.Errorf("delete by struct err:%s", err.Error())
return
}
return err
}