目录
什么是反射
反射的弊端
reflect 包
Go 提供的反射方法
type Type 类型
type Kind 类型
TypeOf
ValueOf
反射(reflection)是在 Java 出现后迅速流行起来的一种概念,通过反射可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。大多数现代的高级语言都以各种形式支持反射功能,反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射。
反射可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
- 代码难以阅读和维护。
- 编译期间不能发现类型错误,有些bug只能在运行很长时间才能发现,可能造成不良后果。
- 反射性能差,通常比正常代码慢一到两个数量级。在对性能要求高或大量反复调用的代码块里建议不要使用反射。
Go语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value 任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。
GoLang reflect 反射官网
type Type interface {
// 在内存中分配时,返回此类型值的对齐方式(以字节为单位)
Align() int
// 当用作结构体中的字段时,返回此类型值的对齐方式(以字节为单位)。
FieldAlign() int
// 返回结构体中的第 i 个方法
Method(i int) Method
// 返回结构体中指定的方法,并返回是否找到该方法的bool值
MethodByName(string) (Method, bool)
// 返回可访问的方法数量
NumMethod() int
// 返回结构体名称
Name() string
// 返回包路径
PkgPath() string
// 返回类型存储所占用的直接大小
Size() uintptr
// 返回类型的字符串表示形式。字符串表示可以使用缩短的包名称,并且不能保证在类型之间是唯一的。要测试类型标识,请直接比较类型。
String() string
// 返回此类型的特定种类
Kind() Kind
// 判断是否实现了指定的接口u
Implements(u Type) bool
// 判断类型的值是否可分配给u类型
AssignableTo(u Type) bool
// 判断类型的值是否可转换为u类型,即使返回true,也可能会宕机,转换类型(切片)长度小于被转换类型的长度可能会宕机
ConvertibleTo(u Type) bool
// 判断此类型的值是否具有可比性。即使Comparable返回true,这种比较仍可能引发宕机。例如,接口类型的值是可比较的,但如果它们的动态类型不可比较,则比较会死机
Comparable() bool
// 返回类型的字节大小
Bits() int
// 返回通道类型的方向。如果这个类型的Kind不是Chan,会宕机
ChanDir() ChanDir
// 判断函数输入类型
IsVariadic() bool
// 返回指针类型的数据类型。如果类型的Kind不是Array、Chan、Map、Pointer或Slice,则会引发宕机
Elem() Type
// 返回结构体种的第 i 个字段
Field(i int) StructField
// 返回与索引相对应的嵌套字段
FieldByIndex(index []int) StructField
// 返回具有给定名称的结构字段,并返回一个布尔值,指示是否找到该字段。
FieldByName(name string) (StructField, bool)
// 以广度优先的顺序考虑结构本身中的字段,然后考虑任何嵌入结构中的字段。在最浅的嵌套深度处停止,嵌套深度包含一个或多个满足匹配函数的字段。如果该深度的多个字段满足匹配函数,则它们会相互抵消,FieldByNameFunc不会返回匹配。此行为反映了Go对包含嵌入字段的结构中的名称查找的处理
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 返回函数类型的第i个输入参数的类型
In(i int) Type
// 返回映射类型的键类型。如果类型的Kind不是Map,会宕机
Key() Type
// 返回数组类型的长度
Len() int
// 返回结构类型的字段数量
NumField() int
// 返回函数类型的输入参数数量
NumIn() int
// 返回函数类型的输出参数数量
NumOut() int
// 返回函数类型的第i个输出参数的类型
Out(i int) Type
}
Kind表示type所表示的特定类型。
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer // ptr
Slice
String
Struct
UnsafePointer
)
func TypeOf(i any) Type | 返回 i 的反射Type类型。如果 i 的值是nil接口,TypeOf返回nil。(返回Type类型后,返回值可以使用上面 type Type interface的方法) |
示例1 reflect.TypeOf 的使用
func getType() {
typeInt := reflect.TypeOf(1)
fmt.Println(typeInt) // 打印 int
fmt.Println(typeInt.String()) // 打印 int
fmt.Println(typeInt.Kind()) // 打印 int
typeString := reflect.TypeOf("hello")
fmt.Println(typeString) // 打印 string
fmt.Println(typeString.String()) // 打印 string
fmt.Println(typeString.Kind()) // 打印 string
}
示例2 reflect.TypeOf 的使用
// 自定义一个数据类型
type User struct {
UserName string `我是Tag`
Age int
}
func getType() {
var u1 User
typeUser1 := reflect.TypeOf(u1)
fmt.Println(typeUser1) // 打印 main.User
fmt.Println(typeUser1.String()) // 打印 main.User
fmt.Println(typeUser1.Kind()) // 打印 struct
fmt.Println(typeUser1.Field(0).Name) // 打印 UserName
var u2 = new(User)
typeUser2 := reflect.TypeOf(u2)
fmt.Println(typeUser2) // 打印 *main.User
fmt.Println(typeUser2.String()) // 打印 *main.User
fmt.Println(typeUser2.Kind()) // 打印 ptr
var u3 = new(User)
typeUser3 := reflect.TypeOf(u3).Elem() // Elem():把指针类型转成普通类型
fmt.Println(typeUser3) // 打印 main.User
fmt.Println(typeUser3.String()) // 打印 main.User
fmt.Println(typeUser3.Kind()) // 打印 struct
}
示例3 获取成员变量详细信息
type User struct {
UserName string `我是Tag`
Age int
student Student
}
type Student struct {
score float32
}
func getField() {
typeUser := reflect.TypeOf(User{})
// 获取成员变量详情
numField := typeUser.NumField()
for i := 0; i < numField; i++ {
field := typeUser.Field(i)
fmt.Println("成员变量详情:", field)
}
// 获取结构体中嵌套结构体student中的变量score
subField := typeUser.FieldByIndex([]int{2, 0}) // []int{2,0} 2是student在User中的下标,0是score在Student中的下标
fmt.Println(subField)
}
示例4 获取成员方法详细信息
type User struct {
UserName string `我是Tag`
Age int
}
func (User) Insert() int {
return 1
}
// 获取成员方法,不是指针方法,如果需要获取指针方法,需要reflect.TypeOf(&User{})取地址才可
func getMethod() {
typeUser := reflect.TypeOf(User{})
numMethod := typeUser.NumMethod()
for i := 0; i < numMethod; i++ {
method := typeUser.Method(i)
fmt.Println("成员方法详情:", method)
}
}
示例5 获取方法入参出参详细信息
func Add(a, b int) int {
return a + b
}
// 获取普通方法详情
func getFunc() {
typeFunc := reflect.TypeOf(Add)
fmt.Println("方法类型:", typeFunc.Kind())
numIn := typeFunc.NumIn()
fmt.Println("方法输入参数个数:", numIn)
numOut := typeFunc.NumOut()
fmt.Println("方法输出参数个数:", numOut)
for i := 0; i < numIn; i++ {
fmt.Printf("第 %d 个输入参数类型是 %s \n", i, typeFunc.In(i))
}
for i := 0; i < numOut; i++ {
fmt.Printf("第 %d 个输出参数类型是 %s \n", i, typeFunc.Out(i))
}
}
func ValueOf(i any) Value | 返回一个新值,初始化为存储在接口中的具体值。ValueOf(nil)返回零。 |
func (v Value) Type() Type | 返回v的Type类型。 |
func (v Value) Kind() Kind | 返回v的类型。如果v是零值(IsValid返回false),Kind返回Invalid。 |
func (v Value) Addr() Value | 返回一个表示v的地址的指针值 // 把普通类型转成指针类型。 |
func (v Value) CanAddr() bool | 判断该值的地址是否可以通过Addr获取。这样的值称为可寻址值。如果值是切片的一个元素,可寻址数组的一个元素,可寻址结构的一个字段,或者指针解引用(elem)的结果,那么它就是可寻址的。如果CanAddr返回false,则调用Addr会出现panic。//是可寻址值的数据才能通过反射进行修改。 |
func (v Value) CanSet() bool | 判断v的值是否可以更改。只有当Value是可寻址的并且不是通过使用未导出(未导出就是首字符小写的意思)的结构字段获得时,才能更改它。如果CanSet返回false,则调用Set或任何类型特定的setter(例如,SetBool、SetInt)将panic。 |
func (v Value) Elem() Value | 返回接口v包含的值或指针v所指向的值。如果v的类型不是接口或指针,它会panic。如果v为nil,则返回0值。// 把指针类型转成普通类型。 |
func (v Value) Interface() (i any) | 返回v的当前值作为interface{}。它相当于:var i interface{} |
func (v Value) IsValid() bool | 报告v是否代表一个值。如果v为零值,则返回false。// var i interface{};fmt.Println(reflect.ValueOf(i).IsValid()) 返回false |
func (v Value) IsNil() bool | 判断参数v是否为空。参数必须是chan、func、interface、map、pointer或slice值 |
func (v Value) IsZero() bool | 判断v是否为其类型的零值。 |
func (v Value) Index(i int) Value | 返回v的第i个元素,是可寻址值。如果v的类型不是数组、切片或字符串,或者i超出了范围,它就会panic。 |
func (v Value) Set(x Value) | 将x赋值给v。如果CanSet返回false,它会panic。x的值Value类型,并且未导出的字段不能赋值。// 设置复合数据类型的时候使用 |
func (v Value) SetInt(x int64) | 将v的底层值设置为x,如果v的Kind不是Int、Int8、Int16、Int32或Int64,或者CanSet()为false,则会panic。 |
func (v Value) SetString(x string) | 将v的底层值设置为x。如果v的Kind不是String或CanSet()为false,则会panic。 |
func (v Value) FieldByName(name string) Value | 返回名称为name的结构体字段。如果未找到字段,则返回零值。如果v的Kind不是结构体,它会panic。 |
func (v Value) Len() int | 返回v的长度。如果v的Kind不是Array、Chan、Map、Slice、String或指向Array的指针,它会panic。 |
func (v Value) SetLen(n int) | 将v的长度设置为n。如果v的Kind不是Slice,或者n为负数或大于Slice的cap,则会panic。 |
func (v Value) SetCap(n int) | 将v的容量设置为n。如果v的Kind不是Slice,或者n小于Slice的len或大于Slice的原来的cap,则会panic。 |
func (v Value) MapIndex(key Value) Value | 返回v中的键关联的值。如果v的Kind不是map,则会宕机。如果在映射中找不到键,或者如果v表示nil映射,则返回零值。与Go中一样,键的值必须可分配给映射的键类型。 |
func (v Value) SetMapIndex(key, elem Value) | 将v中与key相关联的元素设置为elem。如果v的Kind不是Map,它会宕机。如果elem是零值,则SetMapIndex会从映射中删除该键。否则,如果v持有nil映射,则SetMapIndex将宕机。elem的键值对类型必须和v的Map键值对类型保持一致。 |
func (v Value) Send(x Value) | 在通道v上发送x,阻塞的。如果v的Kind不是Chan,或者x的类型与v的元素类型不同,它会宕机。与Go一样,x的值必须是可分配给通道的元素类型(类型要一样)。 |
func (v Value) TrySend(x Value) bool | 尝试在信道v上发送x,但不会阻塞。如果v的Kind不是Chan,它会宕机。它返回是否发送了值。与Go一样,x的值必须是可分配给通道的元素类型。 |
func (v Value) Recv() (x Value, ok bool) | 从通道v接收并返回一个值。如果v的Kind不是Chan,它会宕机。接收将阻塞,直到值准备好为止。如果值x对应于通道上的发送,则布尔值ok为true,如果由于通道关闭而接收到零值,则为false。 |
func (v Value) TryRecv() (x Value, ok bool) | 尝试从信道v接收值,但不会阻塞。如果v的Kind不是Chan,它会宕机。如果receive传递了一个值,那么x是传递的值,ok为true。如果接收不能在无阻塞的情况下完成,则x为零值,ok为false。如果通道是关闭的,则x是通道元素类型的零值,ok为false。 |
func (v Value) Call(in []Value) []Value | 调用函数v。如果v的Kind不是Func,则会panic。它将输出结果作为Value切片返回。与Go一样,每个输入参数都必须可分配给函数对应输入参数的类型。如果v是一个可变函数,Call会创建可变分片参数,复制相应的值。 |
func (v Value) MethodByName(name string) Value | 返回name对应的v中的方法。对返回函数调用的参数不应包括接收器;返回的函数将始终使用v作为接收器。如果没有找到任何方法,它将返回零值。 |
func New(typ Type) Value | 返回一个值,该值表示指向指定类型的新零值的指针。也就是说,返回的Value的Type是PointerTo(典型值)// 就是创建一个对象的Kind类型。 |
func MakeSlice(typ Type, len, cap int) Value | 创建一个指定类型、长度和容量的新的初始化为零的切片。 |
func MakeMap(typ Type) Value | 创建指定类型的map。 |
func MakeMapWithSize(typ Type, n int) Value | 创建一个n容量的指定类型的map。 |
func MakeChan(typ Type, buffer int) Value | 创建一个指定类型和缓冲区大小的新通道。 |
更多API介绍,请查阅官网 | https://golang.google.cn/pkg/reflect |
示例1 reflect.ValueOf 的使用
func getValue() {
intValue := reflect.ValueOf(1)
stringValue := reflect.ValueOf("hello")
userValue := reflect.ValueOf(User{})
fmt.Println(intValue) // 打印 1
fmt.Println(stringValue) // 打印 hello
fmt.Println(userValue) // 打印 { 0}
}
示例2 Value 转 Type
func getValue() {
intValue := reflect.ValueOf(1)
// Value 转 Type
intType := intValue.Type()
fmt.Println(intType)
}
示例3 指针结构体互相转换
func trans() {
userValue := reflect.ValueOf(&User{})
fmt.Println(userValue.Kind()) // 打印 ptr
// 指针 转成 结构体
userValuePtr := userValue.Elem()
fmt.Println(userValuePtr.Kind()) // 打印 struct
// 结构体 转成 指针
userValue2 := userValuePtr.Addr()
fmt.Println(userValue2.Kind()) // 打印 ptr
}
示例4 反射类型转普通类型
func trans() {
iValue := reflect.ValueOf(1)
// 方式一:把 反射类型转成普通类型
iValue.Int()
// 方式二:把 反射类型转成普通类型
iValue2 := iValue.Interface().(int)
fmt.Println(iValue2)
// 把 反射类型 转成 结构体类型
userType := reflect.ValueOf(User{})
user := userType.Interface().(User)
fmt.Println(user.UserName)
}
示例5 通过反射修改基础类型的值
func changeValue() {
var i = 10
iValue := reflect.ValueOf(&i)
fmt.Println(iValue.Kind()) // 打印 ptr
// 判断是否是可寻址值
if iValue.CanAddr() {
iValue.SetInt(20)
fmt.Println("i = ", i) // if 进不来,无打印
}
// 通过 elem 把指针类型解析成反射类型。注意:elem 只能被指针类型的反射所调用,所以在ValueOf的时候带&取址符号
iValue2 := iValue.Elem()
fmt.Println(iValue2.Kind()) // 打印 int
// 判断是否是可寻址值
if iValue2.CanAddr() {
iValue2.SetInt(30)
fmt.Println("i = ", i) // 打印 30
}
}
示例6 通过反射修改结构体成员变量的值
type User struct {
UserName string `我是Tag`
Age int
gender int // 首字母小写是未导出字段,反射不能修改未导出字段
}
func changeValue() {
var user = User{
UserName: "张三",
Age: 18,
}
fmt.Println("修改前的值:", user.UserName, user.Age) // 打印 张三 18
userValue := reflect.ValueOf(&user).Elem()
if userValue.CanAddr() {
userValue.FieldByName("UserName").SetString("李四")
userValue.FieldByName("Age").SetInt(28)
fmt.Println("修改后的值:", user.UserName, user.Age) // 打印 李四 28
}
}
示例7 通过反射修改嵌套结构体成员变量的值
type User struct {
UserName string `我是Tag`
Age int
gender int
Student Student
}
type Student struct {
Score float32
}
func changeValue() {
var user = User{
UserName: "张三",
Student: Student{
Score: 98,
},
}
userValue := reflect.ValueOf(&user).Elem()
if userValue.CanAddr() {
studentValue := userValue.FieldByName("Student")
fmt.Println(studentValue.Kind()) // 打印 struct
// 修改 Score 的值
studentValue.FieldByName("Score").SetFloat(59)
fmt.Println(user.Student.Score) // 打印 59
}
}
示例8 通过反射修改 slice 切片中的值
type User struct {
UserName string `我是Tag`
Age int
gender int // 首字母小写是未导出字段,反射不能修改未导出字段
}
func changeValue() {
var userSlice = make([]*User, 3, 5)
userSlice[0] = &User{
UserName: "张三",
Age: 18,
}
fmt.Println("修改前的数据:", userSlice[0].UserName, userSlice[0].Age) // 打印 张三 18
sliceValue := reflect.ValueOf(userSlice)
// 判断是否是可寻址的
fmt.Println(sliceValue.CanAddr()) // 打印 false
if sliceValue.Len() > 0 {
// 获取切片中第0个元素
sliceValue0 := sliceValue.Index(0)
// 判断是否是可寻址的
fmt.Println(sliceValue0.CanAddr()) // 打印 true
// 查看是否是指针类型,如果是指针类型,需要 elem 解析
fmt.Println(sliceValue0.Kind()) // 打印 ptr 是指针类型
userValue := sliceValue0.Elem()
userValue.FieldByName("UserName").SetString("李四")
userValue.FieldByName("Age").SetInt(28)
fmt.Println("修改后的数据:", userSlice[0].UserName, userSlice[0].Age) // 打印 李四 28
}
// 直接修改整个 user 对象
sliceValue.Index(1).Set(reflect.ValueOf(&User{
UserName: "王五",
Age: 16,
}))
fmt.Println(userSlice[1].UserName, userSlice[1].Age) // 打印 王五 16
}
示例9 通过反射修改 map 中的值
func changeValue() {
u1 := &User{
UserName: "张三",
Age: 18,
}
u2 := &User{
UserName: "李四",
Age: 20,
}
// 定义一个map对象
userMap := make(map[int]*User, 5)
userMap[0] = u1
userMap[1] = u2
// 反射value对象
mapValue := reflect.ValueOf(userMap)
// 把value类型转成type类型
mapType := mapValue.Type()
// 获取map中 key 的数据类型
keyType := mapType.Key()
fmt.Println("key的数据类型是:", keyType) // 打印 int
// 获取map中 value 的数据类型。 注意这里的 elem 是 Type 的方法,作用是获取mapType的元素类型,不是解析指针
valueType := mapType.Elem()
fmt.Println("value的数据类型是:", valueType) //打印 *main.User
// 修改 map中映射对应的值中的某一个成员变量的数据,比如只修改用户的年龄
u1Value := mapValue.MapIndex(reflect.ValueOf(1)) // 修改map中key是1的数据
fmt.Println("修改前的数据:", userMap[1].UserName, userMap[1].Age) // 打印 李四 20
u1Value.Elem().FieldByName("Age").SetInt(28)
fmt.Println("修改后的数据:", userMap[1].UserName, userMap[1].Age) // 打印 李四 28
// 通过反射设置map的键值对
k3 := 2
u3 := &User{
UserName: "王五",
Age: 22,
}
mapValue.SetMapIndex(reflect.ValueOf(k3), reflect.ValueOf(u3))
fmt.Println(userMap[k3].UserName, userMap[k3].Age) // 打印 王五 22
}
示例10 通过反射操作channel管道类型数据
func changeValue() {
// 定义一个 chan 的数据
var ch = make(chan int, 8)
// 向 chan 中写入一行数据
ch <- 10
// 反射chan类型
chanValue := reflect.ValueOf(&ch).Elem()
if chanValue.CanAddr() {
// 读取 chan 管道中的数据
fmt.Println(chanValue.Recv())
}
// 向 chan 管道中写入数据
chanValue.Send(reflect.ValueOf(20))
// 获取把反射类型,转成普通类型
c := chanValue.Interface().(chan int)
fmt.Println(<-c)
}
示例11 通过反射调用普通方法
func callFunc() {
valueFunc := reflect.ValueOf(Add)
fmt.Println(valueFunc.Kind()) // 打印 func
typeFunc := valueFunc.Type()
numIn := typeFunc.NumIn() // 获取参数个数
// 定义函数实参
params := make([]reflect.Value, numIn)
for i := 0; i < numIn; i++ {
params[i] = reflect.ValueOf(1)
}
// 通过反射调用函数,返回切片Value,返回值中是函数返回结果
callResult := valueFunc.Call(params)
for i := 0; i < len(callResult); i++ {
// 打印返回结果 // 实际开发中这里可以使用类型判断
fmt.Println(callResult[i])
}
}
示例12 通过反射调用结构体的成员方法
type User struct {
UserName string `我是Tag`
Age int
gender int
Student Student
}
func (User) Insert(userName string) int {
return 1
}
func callFunc() {
user := &User{}
userValue := reflect.ValueOf(user)
// 通过方法名称获取结构体中的方法
insertMethod := userValue.MethodByName("Insert")
// 调用方法,返回函数结果
callResult := insertMethod.Call([]reflect.Value{reflect.ValueOf("张三")})
for i := 0; i < len(callResult); i++ {
// 打印返回结果
fmt.Println(callResult[i])
}
}
示例13 通过反射创建结构体对象实例
func newStruct() {
u := reflect.TypeOf(User{})
// new一个对象的指针
value := reflect.New(u)
// 赋值
value.Elem().FieldByName("UserName").SetString("张三")
// 赋值
value.Elem().FieldByName("Age").SetInt(18)
// 反射类型转普通类型
user := value.Interface().(*User)
fmt.Println(user.UserName, user.Age) // 打印 张三 18
}
示例14 通过反射创建slice切片实例
func newSlice() {
var slice []User
sliceType := reflect.TypeOf(slice)
// 创建一个指针类型的切片
sliceValue := reflect.MakeSlice(sliceType, 3, 5)
sliceValue.Index(0).Set(reflect.ValueOf(User{
UserName: "张三",
Age: 18,
}))
// 把反射的类型转成 普通类型
users := sliceValue.Interface().([]User)
for _, user := range users {
fmt.Println(user.UserName, user.Age) // 打印 张三 18,0,0
}
}
示例15 通过反射创建map实例
func newMap() {
var userMap map[int]*User
mapType := reflect.TypeOf(userMap)
// 创建map
mapValue := reflect.MakeMap(mapType)
//reflect.MakeMapWithSize(mapType,5) // 指定容量
// 给对象赋值
u := &User{
UserName: "张三",
Age: 18,
}
mapValue.SetMapIndex(reflect.ValueOf(0), reflect.ValueOf(u))
mp := mapValue.Interface().(map[int]*User)
// 遍历对象
for k, v := range mp {
fmt.Printf("下标:%d, 数据:%s %d", k, v.UserName, v.Age)
}
}
示例16 通过反射创建channel管道实例
func newChannel() {
var ch chan User
chanType := reflect.TypeOf(ch)
// 创建 chan 对象
chanValue := reflect.MakeChan(chanType, 5)
u := User{
UserName: "张三",
Age: 18,
}
// 向 chan 中添加数据
chanValue.Send(reflect.ValueOf(u))
// 将反射类型转成普通类型
c := chanValue.Interface().(chan User)
fmt.Println(<-c)
}
全套教程地址:Golang全套教程