反射可以在运行时动态地检查变量和类型,并且可以调用变量和类型的方法、获取和修改变量的值和类型等。使用反射可以使程序更加灵活,但也需要谨慎使用
Go语言的反射主要涉及两个重要的类型:reflect.Type
和reflect.Value
。其中,reflect.Type
表示类型信息,包括类型的名称、方法和字段等;reflect.Value
表示值信息,包括值的类型和内容等。通过这两个类型,我们可以对变量和类型进行各种反射操作。通过refect包下的reflect.TypeOf()
和reflect.ValueOf()
两个函数来获取对象的Value
和Type
。
reflect.TypeOf()
:用于获取变量的数据类型
func main() {
// 获取x的数据类型,并赋值给t
var x float64 = 3.14
fmt.Println(reflect.TypeOf(x)) // float64
fmt.Printf("%T", x) // float64 Printf底层就是使用了反射,所以不管传入什么类型都能获取到
}
在反射中关于类型还划分为两种:
类型(Type)
:类型指的是变量的数据类型
种类(Kind)
:种类是语言原生数据类型,用于区分指针、结构体、数组、切片等类型。
type Myint int
type MyStruct struct {
Name string
age int
}
var x Myint = 123
var y *Myint = &x
var z MyStruct = MyStruct{"itzhuzhu", 20}
var w *MyStruct = &z
func main() {
fmt.Println(reflect.TypeOf(x)) // main.Myint
fmt.Println(reflect.TypeOf(y)) // *main.Myint
fmt.Println(reflect.TypeOf(z)) // main.MyStruct
fmt.Println(reflect.TypeOf(w)) // *main.MyStruct
// Kind:
fmt.Println(reflect.TypeOf(x).Kind()) // int
fmt.Println(reflect.TypeOf(y).Kind()) // ptr
fmt.Println(reflect.TypeOf(z).Kind()) // struct
fmt.Println(reflect.TypeOf(w).Kind()) // ptr
}
Kind源码:
// 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 // 底层指针
)
reflect.ValueOf()
:用于获取变量的值
func main() {
var x float64 = 3.14
fmt.Println(reflect.ValueOf(x)) // 3.14
}
通过反射获取变量的值
func reflectValue(x interface{}) {
v := reflect.ValueOf(x) // 获取接口x的值
k := v.Kind() // 获取接口x的种类
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("数据类型为Int64 值是:%d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
fmt.Printf("数据类型为Float32 值是:%f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
fmt.Printf("数据类型为Float64 值是:%f\n", float64(v.Float()))
}
}
func main() {
var a float32 = 3.14
var b int64 = 100
reflectValue(a) // 数据类型为Float32 值是:3.140000
reflectValue(b) // 数据类型为Int64 值是:100
/*
在这里,reflect.ValueOf(10)的作用是将整数10转换为一个reflect.Value类型的值,
该值包含整数10的类型和值等信息。具体来说,它返回一个reflect.Value类型的值c,
该值表示整数10,并且可以使用反射获取它的类型和值等信息。通过使用reflect.ValueOf(),
我们可以将一个普通的值转换为反射类型的值,从而使用反射对它进行进一步处理。
*/
c := reflect.ValueOf(10)
fmt.Printf("c的数据类型是:%T\n", c) // c的数据类型是:reflect.Value
}
要修改变量的值,需要使用reflect.Value
类型的Elem()
方法获取到变量的指针,然后再使用reflect.Value
类型的Set()
方法修改值。
func main() {
var x float64 = 3.14
v := reflect.ValueOf(&x).Elem()
// 也可以修改为其它数据类型,使用
v.SetFloat(3.15)
fmt.Println(x) // 3.15
}
isNil()
:返回v的值是否为nil,v的值的类型必须是通道、函数、接口、映射、指针、切片之一,否则IsNil函数会导致panic。
func (v Value) IsNil() bool
IsValid()
:返回v是否有值,如果v是零值返回false,此时v除了IsValid、String、Kind之外的方法都会导致panic。
func (v Value) IsValid() bool
演示:
func main() {
// *int类型空指针
var a *int
fmt.Println("a是否为空:", reflect.ValueOf(a).IsNil())
// nil值
fmt.Println("a是否有值:", reflect.ValueOf(a).IsValid())
fmt.Println("测试传入nil值:", reflect.ValueOf(nil).IsValid())
// 实例化一个匿名结构体
b := struct{}{}
// 判断结构体中是否有abc字段
fmt.Println("struct是否存在abc字段:", reflect.ValueOf(b).FieldByName("abc").IsValid())
// 判断结构体中是否有abc方法
fmt.Println("struct是否存在abc方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
// map
c := map[string]int{}
// 判断map中的key是否有abc
fmt.Println("map中是否有abc这个key:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("abc")).IsValid())
}
输出:
a是否为空: true
a是否有值: true
测试传入nil值: false
struct是否存在abc字段: false
struct是否存在abc方法: false
map中是否有abc这个key: false
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息 |
NumField() int | 返回结构体成员字段数量 |
NumMethod() int | 返回结构体方法的数量 |
Method(int) Method | 返回该类型的第i个方法 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 |
MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 |
StructField
类型用来描述结构体中的一个字段的信息。
type StructField struct {
Name string // Name是字段的名字。
PkgPath string // PkgPath是非导出字段的包路径,对导出字段该字段为""
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于Type.FieldByIndex时的索引切片
Anonymous bool // 是否匿名字段
}
演示:
type student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (s student) StudentMethod() {
fmt.Println("我是student的方法")
}
func main() {
stu := student{
Name: "韩信",
Age: 18,
}
t := reflect.TypeOf(stu)
fmt.Println("根据索引返回字段信息:", t.Field(0))
fmt.Println("结构体的名称:", t.Name())
fmt.Println("结构体的类型:", t.Kind())
fmt.Println("结构体的成员数量:", t.NumField())
fmt.Println("结构体的方法数量:", t.NumMethod())
fmt.Println("返回第i个方法:", t.Method(0))
name, b := t.FieldByName("Name")
fmt.Println("字段信息为:", name, "字段是否存在:", b)
// 也可以使用if去判断,如果ok为true则执行
if nameFiled, ok := t.FieldByName("Name"); ok {
fmt.Println("字段信息为:", nameFiled, "字段是否存在:", ok)
}
// 通过for循环遍历结构体的所有字段信息
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Println(field.Name, field.PkgPath, field.Type, field.Tag.Get("json"), field.Index, field.Offset, field.Anonymous)
}
// 通过字段名获取指定结构体字段信息
if field, ok := t.FieldByName("Name"); ok {
fmt.Println(field.Name, field.PkgPath, field.Type, field.Tag.Get("json"), field.Index, field.Offset, field.Anonymous)
}
}
输出:
根据索引返回字段信息: {Name string json:"name" 0 [0] false}
结构体的名称: student
结构体的类型: struct
结构体的成员数量: 2
结构体的方法数量: 1
返回第i个方法: {StudentMethod func(main.student) <func(main.student) Value> 0}
字段信息为: {Name string json:"name" 0 [0] false} 字段是否存在: true
字段信息为: {Name string json:"name" 0 [0] false} 字段是否存在: true
Name string name [0] 0 false
Age int age [1] 16 false
Name string name [0] 0 false
演示:
type student struct {
Name string
Age int
Address Address
}
type Address struct {
City string
Country string
}
func (p *student) SayHello() {
fmt.Printf("你好,我是野王 %s\n", p.Name)
}
func main() {
p := student{
Name: "韩信",
Age: 30,
Address: Address{
City: "野区",
Country: "王者荣耀",
},
}
// 使用 FieldByIndex 获取结构体中嵌套的字段值,返回第3个字段的第0个信息
cityField := reflect.ValueOf(p).FieldByIndex([]int{2, 0})
fmt.Println(cityField.Interface()) // 野区
// 使用 FieldByNameFunc 根据字段名获取字段值
ageFieldVal := reflect.ValueOf(p).FieldByNameFunc(func(name string) bool {
return name == "Age"
})
if ageFieldVal.IsValid() {
ageField := ageFieldVal.Interface().(int)
fmt.Println(ageField) // 30
}
// 使用 MethodByName 调用结构体中的方法
method := reflect.ValueOf(&p).MethodByName("SayHello")
method.Call(nil) // 你好,我是野王 韩信
}