reflect.Value 和 reflect.Type
反射有两种类型 reflect.Value 和 reflect.Type ,分别表示变量的值和类型,并且提供了两个函数 reflect.ValueOf 和 reflect.TypeOf 分别获取任意对象的 reflect.Value 和 reflect.Type。
reflect.Value
reflect.Value 可以通过函数 reflect.ValueOf 获得。reflect.Value 被定义为一个 struct 结构体:
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
typ: 用于保存值的类型信息。在 Go 的 reflect 包中,rtype 结构表示类型信息。
ptr: 用于保存数据的指针或指向数据的指针。如果 flagIndir 标志被设置,ptr 就是指向数据的指针;否则,ptr 就是数据的指针。
flag: 用于保存与值相关的元信息和标志位。flag 的最低的几位用于表示标志位,其余的位用于存储类型的信息和其他元数据。flag 的位域包括:
- flagStickyRO: 表示该值是通过不导出的非嵌入字段获得的,因此是只读的。
- flagEmbedRO: 表示该值是通过导出的嵌入字段获得的,因此是只读的。
- flagIndir: 标志 val 持有指向数据的指针。
- flagAddr: v.CanAddr 为 true,表示该值的地址可以被取得(implies flagIndir)。
- flagMethod: 表示该值是一个方法值。
- 低几位表示值的种类(Kind)。
- 其余的位用于存储方法值的方法号。
可以看到,这个结构体中的字段都是私有的,我们只能使用 reflect.Value 的方法,部分方法比如有:
方法分为三类:
- 获取和修改对应的值
- struct 类型的字段有关,用于获取对应的字段
- 类型上的方法集有关,用于获取对应的方法
任意类型的对象 与 reflect.Value 互转
func main() {
i := 5
//int to reflect.Value
iv:=reflect.ValueOf(i)
//reflect.Value to int
i1 := iv.Interface().(int)
fmt.Println(i1)
}
//运行结果:
5
- i := 5:创建一个整数变量 i 并初始化为 5。
- iv := reflect.ValueOf(i):使用 reflect.ValueOf 函数将整数 i 转换为 reflect.Value 类型。iv 现在是一个包含整数值的 reflect.Value 对象。
- i1 := iv.Interface().(int):使用 Interface() 方法将 reflect.Value 转换为接口类型,并通过断言将其还原为原始的整数类型。这里假设 iv 确实包含一个整数值,因此使用 (int) 进行断言。如果类型不匹配,这会导致运行时恐慌。
- fmt.Println(i1):打印还原后的整数值。
修改对应的值
func main() {
i := 5
ipv := reflect.ValueOf(&i)
ipv.Elem().SetInt(6)
fmt.Println(i)
}
//运行结果:
6
- 示例中我们通过反射修改了一个变量。
- reflect.ValueOf 函数返回的是一份值的拷贝,所以我们要传入变量的指针才可以。
- 因为传递的是一个指针,所以需要调用 Elem 方法找到这个指针指向的值,这样才能修改。
- 要修改一个变量的值,关键点:传递指针(可寻址),通过 Elem 方法获取指向的值,才可以保证值可以被修改,reflect.Value 为我们提供了 CanSet 方法判断是否可以修改该变量。
修改 struct 结构体字段的值
func main() {
p := person{Name: "微客鸟窝",Age: 18}
pv:=reflect.ValueOf(&p)
pv.Elem().Field(0).SetString("无尘")
fmt.Println(p)
}
type person struct {
Name string
Age int
}
//运行结果:
{无尘 18}
步骤总结:
- 传递一个 struct 结构体的指针,获取对应的 reflect.Value;
- 通过 Elem 方法获取指针指向的值;Elem 方法是在 Go 中 reflect.Value 类型上的一个方法,它用于获取接口或指针类型 reflect.Value 中所包含的值。如果 v 是一个指向某个值的指针,Elem 方法会返回该指针指向的值;如果 v 是一个接口类型,Elem 方法会返回接口中包含的具体值。
- 通过 Field 方法获取要修改的字段;
通过反射修改一个值的规则:
可被寻址,通俗地讲就是要向 reflect.ValueOf 函数传递一个指针作为参数。
如果要修改 struct 结构体字段值的话,该字段需要是可导出的,而不是私有的,也就是该字段的首字母为大写。
记得使用 Elem 方法获得指针指向的值,这样才能调用 Set 系列方法进行修改。
获取对应的底层类型
因为我们可以通过 type 关键字来自定义一些新的类型,而底层类型就是一些基础类型。比如上面示例中的 p 变量类型为 person,底层类型为 struct 结构体类型。
type person struct {
Name string
Age int
}
func main() {
p := person{Name: "微客鸟窝", Age: 18}
p1 := reflect.ValueOf(p)
p2 := reflect.ValueOf(&p)
fmt.Println(p1.Kind())
fmt.Println(p2.Kind())
}
//运行结果:
struct
ptr
Kind 方法返回一个 Kind 类型的值,它是一个常量,从源码看下定义的 Kind 常量列表,含了 Go 语言的所有底层类型:
一共 26 种,我们可以分类如下:
- 基础类型Bool、String以及各种数值类型(有符号整数Int/Int8/Int16/Int32/Int64,无符号整数Uint/Uint8/Uint16/Uint32/Uint64/Uintptr,浮点数Float32/Float64,复数Complex64/Complex128)
- 复合(聚合)类型Array和Struct
- 引用类型Chan、Func、Ptr、Slice和Map(值类型和引用类型区分不明显,这里不引战,大家理解意思就行)
- 接口类型Interface
- 非法类型Invalid,表示它还没有任何值(reflect.Value的零值就是Invalid类型)
Field和NumField
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
Address string
}
func main() {
p := Person{"Alice", 30, "123 Main St"}
// 使用 reflect.ValueOf 获取结构体实例的反射值
pValue := reflect.ValueOf(p)
// 获取结构体字段的个数
numFields := pValue.NumField()
fmt.Println("Number of fields:", numFields)
// 遍历结构体的字段
for i := 0; i < numFields; i++ {
// 获取第 i 个字段的反射值
fieldValue := pValue.Field(i)
// 输出字段名和值
fieldName := pValue.Type().Field(i).Name
fmt.Printf("Field %s: %v\n", fieldName, fieldValue.Interface())
}
}
//输出结果
Number of fields: 3
Field Name: Alice
Field Age: 30
Field Address: 123 Main St