反射(reflection)是一种能够检查程序在运行时的变量类型和值的机制。Go的反射机制定义在reflect包中。使用反射,可以动态地调用对象的方法或访问其字段,即使在编写代码时并不知道这些方法或字段的具体存在
反射主要涉及到reflect.Type
和reflect.Value
这两个类型。reflect.Type
代表Go
值的类型,而reflect.Value
代表Go
值的具体值
在使用反射时需要注意的是,反射操作通常比直接操作要慢,因此在性能敏感的代码中应当谨慎使用反射。另外,过度使用反射可能会使代码难以理解和维护
基本步骤是通过reflect.ValueOf()
函数获取反射值对象,然后使用该对象的方法来获取具体的值
package main
import (
"fmt"
"reflect"
)
func main() {
var (
x float64 = 11.2
y int = 55
z 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 main
import (
"fmt"
"reflect"
)
func main() {
var x = 2.7
fmt.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 int
Field2 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 main
import (
"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 main
func 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 main
import (
"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
}