38、Go语言基础之反射
hi,我是温新
什么是反射
反射(Reflection)是一种编程技术,它允许程序在运行时检查和操作变量、方法、结构等程序结构的能力。
Go语言中也提供了反射机制,通过reflect
包可以实现这一功能。
反射的基本概念:
反射是一种在运行时检查和操作程序结构的技术。它允许你在程序运行时动态地查看和修改变量、类型和函数等信息。
reflect
包:
在Go语言的反射机制中,任何接口值都由是一个具体类型
和具体类型的值
两部分组成的。Go reflect
包提供了处理反射的工具。你可以使用这个包来获取变量的类型、值,检查方法和字段等。
反射的基本操作
TypeOf
在 Go 语言中,使用 reflect.TypeOf
获取任意值的类型对象。
package main
import (
"fmt"
"reflect"
)
func main() {
var name string = "王美丽"
var age int8 = 19
reflectType(name) // 类型为:string
reflectType(age) // 类型为:int8
}
// 定义一个函数,用于获取变量的类型
func reflectType(data interface{}) {
v := reflect.TypeOf(data)
fmt.Printf("类型为:%v\n", v)
}
type name 和 type kind
在反射中关于类型还划分为两种:类型(Type)
和种类(Kind)
。在 Go 语言中,可以使用 type 关键字构造很多自定义类型,而 Kind 就是指底层的类型,但是在反身中,当需要区分指针、结构体等大品种的类型时,就会用到 Kind。
package main
import (
"fmt"
"reflect"
)
func main() {
var x *float32 // 指针
var y myInt // 自定义类型
var z rune // 类型别名
getReflectType(x)
getReflectType(y)
getReflectType(z)
var lucy = user{"露西"}
getReflectType(lucy)
}
type myInt int
type user struct {
name string
}
func getReflectType(data interface{}) {
v := reflect.TypeOf(data)
fmt.Printf("类型为:%v,种类为:%v\n", v.Name(), v.Kind())
}
输出结果:
类型为:,种类为:ptr
类型为:myInt,种类为:int
类型为:int32,种类为:int32
类型为:user,种类为:struct
Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()
都是返回空
。
reflect
包中定义的 Kind 类型有:
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
ValueOf
reflect.ValueOf
的返回值是 reflect.Value
类型,其包含了原始值的值信息。而reflect.Value
与原始值之间可以互相转换。
reflect.Value
类型提供的获取原始值的方法如下:
方法 | 说明 |
---|---|
Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转为指定类型 |
Int() int64 | 将值以 int 类型返回,所有有符号整型都可以此方式返回 |
Uint() uint64 | 将值以 uint 类型返回,所有无符号整型都可以此方式返回 |
Float() float64 | 将值以 float64 类型返回,所有浮点数都可以此方式返回 |
Bool() bool | 将值以 bool 类型返回 |
Bytes() []bytes | 将值以 字节数组 []bytes 类型返回 |
String() string | 将值以字符串类型返回 |
通过反射获取值
package main
import (
"fmt"
"reflect"
)
func main() {
var x float32 = 3.14
var y int64 = 100
GetReflectValue(x) // 类型为 float32,值为 3.140000
GetReflectValue(y) // 类型为 Int64,值为 100
z := reflect.ValueOf(1)
fmt.Printf("类型 z 为:%T\n", z) // 类型 z 为:reflect.Value
}
func GetReflectValue(data interface{}) {
// 通过反射获取值
v := reflect.ValueOf(data)
// 获取值的类型
k := v.Kind()
switch k {
case reflect.Int64:
// v.Int() 从反射中获取整型的原始值,再强制转类型
fmt.Printf("类型为 Int64,值为 %d\n", int64(v.Int()))
case reflect.Float32:
fmt.Printf("类型为 float32,值为 %f\n", float32(v.Float()))
case reflect.Float64:
fmt.Printf("类型为 float64,值为 %f\n", float64(v.Float()))
}
}
通过反射设置值
要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量的值。但是在反射中使用Elem()
方法来获取指针对应的值。案例如下:
package main
import (
"fmt"
"reflect"
)
func main() {
name := "王美丽"
reflectSetValue2(&name)
fmt.Println(name) // 王大丽
reflectSetValue(name) // 报错
}
func reflectSetValue(data interface{}) {
v := reflect.ValueOf(data)
if v.Kind() == reflect.String {
// 不能修改值,因为函数调用是值的拷贝,只有传递指针时才能修改对应的值
v.SetString("王大丽1")
}
}
func reflectSetValue2(data interface{}) {
v := reflect.ValueOf(data)
// 反射中通过 eLem 方法获取指针对应的值
if v.Elem().Kind() == reflect.String {
v.Elem().SetString("王大丽")
}
}
isNil() 和 isValid()
isNil()
func (v value) IsNil() bool
IsNil()
方法用于检查反射值是否为 nil,通常用于指针类型。如果反射值代表一个 nil 指针,则 IsNil()
返回 true
,否则返回 false
。
对于非指针类型的反射值,调用 IsNil()
方法会引发 panic。
isValid()
func (v value) IsValid() bool
IsValid()
方法用于检查反射值是否有效,即是否引用了一个合法的 Go 值。如果反射值有效,它返回 true
,否则返回 false
。v 除了IsValid、String、Kind之外的方法都会导致 panic。
一个反射值在以下情况下被认为是无效的:
1、它是一个零值。
2、它是一个未初始化的反射值。
3、它表示一个不允许被读取或修改的值,例如不可导出的字段。
案例如下:
package main
import (
"fmt"
"reflect"
)
func main() {
// 定义一个空指针
var x *int
fmt.Println("变量 x 的值是 IsNil:", reflect.ValueOf(x).IsNil()) // true
// nil 值
fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid()) // false
// 实例化一个匿名结构体
y := struct {
}{}
// 在结构体查找 id 字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(y).FieldByName("id").IsValid()) // false
// 在结构体中查找方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(y).MethodByName("getName").IsValid()) // false
// map
z := map[string]int{}
// map 中查找一个不存在的 key
fmt.Println("map 中不存在的 key:", reflect.ValueOf(z).MapIndex(reflect.ValueOf("美丽")).IsValid()) // false
}
输出结果:
变量 x 的值是 IsNil: true
nil IsValid: false
不存在的结构体成员: false
不存在的结构体方法: false
map 中不存在的 key: false
结构体反射
通过 reflect.TypeOf()
可以获得反射对象信息,若它的类型是结构体,可以通过反射值对象(reflect.Type
) 的 NumField()
和 Field()
方法来获取结构体成员的详细信息。
reflect.Type
中与获取结构体成员相关的方法:
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段信息 |
NumField() int | 返回结构体成员字段数量 |
FieldByName(name string)(StructField, bool) | 根据给定字段串返回字符串对应的结构栓体字段信息 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据[]int提供的每个结构体的字段索引,返回字段的信息 |
FieldByNameFunc(match func(string) bool)(StructField, bool) | 根据传入的匹配函数匹配需要的字段 |
NumMethod() int | 返回该类型的方法集中方法的数目 |
Method(int)Method | 返回该类型方法集中的第 i 个方法 |
MethodByName(string)(Method, bool) | 根据方法名返回该类方法集中的方法 |
StructField 类型
structField
类型用来描述结构体中的一个字段的信息,其定义如下:
type StructField struct {
// Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
// 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于Type.FieldByIndex时的索引切片
Anonymous bool // 是否匿名字段
}
结构体反射案例如下:
package main
import (
"fmt"
"reflect"
)
func main() {
stu1 := Student{"王美丽", 100}
t := reflect.TypeOf(stu1)
fmt.Println(t.Name(), t.Kind())
// 遍历结构体所有字段信息
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("名字:%s 索引:%d,类型:%v json 标签:%v\n", field.Name, field.Index, field.Type, field.Tag)
}
// 通过字段名获取指定结构体字段信息
if scoreField, ok := t.FieldByName("score"); ok {
fmt.Printf("名字:%s 索引:%d 类型:%v json 标签:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
printMethod(stu1)
}
type Student struct {
name string `json:"name"`
score int `json:"score"`
}
func (s Student) Study() string {
msg := "好好学习,天天向上"
fmt.Println(msg)
return msg
}
func (s Student) Sleep() string {
msg := "太阳当空照"
fmt.Println(msg)
return msg
}
// 通过反射获取方法
func printMethod(data interface{}) {
t := reflect.TypeOf(data)
v := reflect.ValueOf(data)
fmt.Println(t.NumMethod())
for i := 0; i < v.NumField(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("方法名:%s\n", t.Method(i).Name)
fmt.Printf("方法:%s\n", methodType)
// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
var args = []reflect.Value{}
v.Method(i).Call(args)
}
}
输出结果:
Student struct
名字:name 索引:[0],类型:string json 标签:json:"name"
名字:score 索引:[1],类型:int json 标签:json:"score"
名字:score 索引:[1] 类型:int json 标签:score
2
方法名:Sleep
方法:func() string
太阳当空照
方法名:Study
方法:func() string
好好学习,天天向上
反射是把双刃剑
反射在 Go 语言中是一项强大的功能,但也是需要谨慎使用的。以下是一些原因解释为什么反射需要小心使用:
- 1、基于反射的代码比较脆弱,其反射中的类型错误是在代码真正运行时才触发
panic
; - 2、大量使用反射,代码将变得难以理解;
- 3、反射会增加性能开销,因为它在运行时进行类型检查。