前言
反射(reflection)是一种能够检查程序在运行时的变量类型和值的机制。Go的反射机制定义在reflect包中。使用反射,可以动态地调用对象的方法或访问其字段,即使在编写代码时并不知道这些方法或字段的具体存在
反射主要涉及到reflect.Type
和reflect.Value
这两个类型。reflect.Type
代表Go
值的类型,而reflect.Value
代表Go
值的具体值
在使用反射时需要注意的是,反射操作通常比直接操作要慢,因此在性能敏感的代码中应当谨慎使用反射。另外,过度使用反射可能会使代码难以理解和维护
获取值
基本步骤是通过reflect.ValueOf()
函数获取反射值对象,然后使用该对象的方法来获取具体的值
package mainimport ("fmt""reflect"
)func main() {var (x float64 = 11.2y int = 55z string = "hello")// 获取x的反射值对象valueOfX := reflect.ValueOf(x)// 获取实际的float64值fmt.Println("value of x:", valueOfX.Float())// 获取y的反射值对象valueOfY := reflect.ValueOf(y)// 获取实际的int值fmt.Println("value of y:", valueOfY.Int())// 获取z的反射值对象valueOfZ := reflect.ValueOf(z)// 获取实际的string值fmt.Println("value of z:", valueOfZ.String())
}
创建了三个变量x、y和z,分别是float64、int和string类型。对于每个变量,用reflect.ValueOf()获取其反射值对象,并根据变量的基本类型,使用相应的方法来获取实际的值。例如,对于float64类型的变量,我们调用Float()方法来获取它的值。类似地,对于int和string,我们分别调用Int()和String()方法
反射值对象reflect.Value
提供了一系列的方法来获取具体类型的值,包括但不限于Bool()
、Bytes()
、Complex()
、Float()
、Int()
、String()
等。每个方法对应于原始值的类型,尝试获取与值类型不匹配的类型会导致运行时panic
修改值
使用reflect
包可以修改反射对象的值,须满足以下条件:
- 反射对象
reflect.Value
必须是可寻址的(可设置的) - 反射对象对应的值必须是可导出的(即字段名首字母大写)
要修改一个变量的值,需要通过reflect.ValueOf
获得反射值对象,并通过.Elem()
方法获取指针指向的值的反射值对象
package mainimport ("fmt""reflect"
)func main() {var x = 2.7fmt.Println("before:", x)// 获取变量x的反射值对象p := reflect.ValueOf(&x) // 注意:这里传入的是x的地址// 获取反射值对象对应的值(需要是可设置的)v := p.Elem()// 判断反射值对象是否可以改变if v.CanSet() {// 修改反射值对象的值v.SetFloat(11.3)}fmt.Println("after:", x)
}
首先获取x的地址的反射对象p,然后通过调用p.Elem()获取x值的反射对象v。由于v是通过指针获取的,所以它是可寻址的,然后通过v.SetFloat来修改x的值
reflect.Type()
和 reflect.Kind()
的使用
reflect.Type
表示一个Go
值的具体类型,包括其名称和包路径。这个类型是接口类型,提供了关于Go值的详细类型信息,比如结构体的字段信息、一个函数的签名等
reflect.Kind
则表示一个Go
值的基本分类,或者说是其底层类型。在Go
语言中,基本分类包括如int
、float64
、bool
、struct
、slice
等。每一个 reflect.Type
都对应一个 reflect.Kind
,但是不同的 reflect.Type
可能对应相同的 reflect.Kind
。例如,所有结构体类型的 reflect.Kind
都是 reflect.Struct
举例说明
type MyStruct struct {Field1 intField2 string
}s := MyStruct{Field1: 10, Field2: "Hello"}
v := reflect.ValueOf(s)fmt.Println("type:", v.Type()) // 输出s的具体类型,例如 "main.MyStruct"
fmt.Println("kind:", v.Kind()) // 输出s的基本分类,例如 "struct"
结构体中的反射
- 获取值
获取到反射值对象后,可以是通过.Field()
拿到元数据,然后点其中的方法,拿到字段属性
package mainimport ("fmt""reflect"
)type User struct {ID int `json:"id"`Name string `json:"name"`Age int `json:"age,omitempty"`weight float64
}func GetUser(user User) {t := reflect.TypeOf(user) // 获取user变量的类型对象v := reflect.ValueOf(user) // 获取user变量的值对象for i := 0; i < v.NumField(); i++ { // 以结构体字段个数,遍历f := t.Field(i) // 获取第i个字段的元数据, 以第二个字段为例:{Name string json:"name" 8 [1] false}fmt.Println(f.Name, f.Type, f.Tag, f.Offset, f.Index, f.Anonymous, f.PkgPath) // 下面解释提到fmt.Printf("%#v\n", f.Tag.Get("json")) // 打印字段的json标签值fmt.Println(v.Field(i)) // 获取第i个字段的值fmt.Println("----------------------------")}}func main() {user := User{ID: 1,Name: "张三",Age: 20,weight: 45.5,}GetUser(user)
}
运行后,控制台输出:
ID int json:"id" 0 [0] false
"id"
1
----------------------------
Name string json:"name" 8 [1] false
"name"
张三
----------------------------
Age int json:"age,omitempty" 24 [2] false
"age,omitempty"
20
----------------------------
weight float64 32 [3] false main
""
45.5
----------------------------
字段元数据中的其他字段属性解释:
f.Name
: 字段名称,例如"Name"
f.Type
: 字段的类型,例如reflect.TypeOf("")
表示string
类型f.Tag
: 字段的标签(Tag),是结构体中字段后定义的额外信息,例如json:"name"
f.Offset
: 字段在结构体内存中的偏移量,表示该字段在结构体中的起始字节位置。f.Index
: 字段的索引切片,表示嵌套字段的访问索引。对于顶层字段,这将是单个元素的切片,例如[1]
f.Anonymous
: 表示字段是否是匿名字段(即嵌入字段),布尔值true
或false
f.PkgPath
: 如果字段是非导出的(即小写开头),则为字段的包路径。如果字段是导出的(即大写开头),它将是空字符串。我的例子中,就是main
- 再来看看修改值
首先&
传入结构体指针,然后.Elem()
获取指针指向的值的反射值对象,再然后就同取值一样,.Field()
之类的,最后修改就是.SetInt
,.SetFloat
,SetString
等这些,同普通类型的获取值基本一致
package mainfunc SetUser(user *User) { // 或者用 any 来替代*User,但应处理异常v := reflect.ValueOf(user).Elem()v.FieldByName("Age").SetInt(22)
}
func main() {user := User{ID: 1,Name: "张三",Age: 20,weight: 45.5,}SetUser(&user)fmt.Println(user.Age) // 22}
这里需要注意:设置的字段需要是可导出的(大写)且可寻址的,否则会引发 panic
- 反射调用结构体的方法
经过前面学习,我们知道字段是通过.Field()
之类的方法来找到的,同样地,方法是通过.Method()
之类的方法来找到
调用结构体方法,是通过.Call(in []Value) []Value
来实现
package mainimport ("fmt""reflect"
)type MyStruct struct {Value int
}// Add 是一个指针接收器方法
func (s *MyStruct) Add(amount int) {s.Value += amount
}func main() {// 创建结构体实例instance := MyStruct{Value: 100}// 获取reflect.Value类型的实例val := reflect.ValueOf(&instance)// 寻找方法method := val.MethodByName("Add")// 检查方法是否存在if !method.IsValid() {fmt.Println("Method not found.")return}// 参数必须作为reflect.Value的切片传入args := []reflect.Value{reflect.ValueOf(20)}// 调用方法method.Call(args)// 输出结果fmt.Println(instance.Value) // 120
}