反射
允许程序在运行时检查、修改自身的结构和行为。反射通过 reflect
包实现,主要处理类型(Type
)和值(Value
)的运行时操作。
概念
一般情况下,在编写代码的时候,我们自己定义的结构体肯定知道它有什么样的变量和方法,能够进行特定的处理。但是对于程序代码来说,如果传入的是 interface{} 之类的类型,它们并不知道它拥有什么样的变量和方法,因此,这时候我们就可以利用反射窥探它的元数据,获取它的变量和方法,然后根据这些信息进行特定的处理,比如判断是否存在某个变量,如果存在就做什么样的处理。。。
常用场景
- 不能明确接口调用哪个函数,需要根据传入的参数在运行时决定。
- 不能明确传入函数的参数类型,需要在运行时处理任意对象。
核心类型
reflect.Type
:表示 Go 的类型信息(通过reflect.TypeOf()
获取)reflect.Value
:表示 Go 的值信息(通过reflect.ValueOf()
获取)
1import "reflect"
2
3func main() {
4 var x float64 = 3.4
5 fmt.Println("Type:", reflect.TypeOf(x)) // float64
6 fmt.Println("Value:", reflect.ValueOf(x)) // 3.4
7}
Type
1type Type interface {
2 // 适用于所有类型
3 // 返回该类型内存对齐后所占用的字节数
4 Align() int
5 // 返回该类型的方法集中的第 i 个方法
6 Method(int) Method
7 // 根据方法名获取对应方法集中的方法,无法获取私有方法
8 MethodByName(string) (Method, bool)
9 // 返回该类型的方法集中导出的方法的数量,注意是可导出的,私有方法无法获取
10 NumMethod() int
11 // 返回该类型的名称,比如 People、int
12 Name() string
13 // 获取所在的包名
14 PkgPath() string
15 // 返回 "PkgPath().Name()"
16 String() string
17 // 返回该类型的类型,这个类型是笼统的,比如 int、struct、func
18 Kind() Kind
19 // 获取第 i 个变量信息(编译期间就已经将所有的变量都计算好存储为元数据了,类似 Java 已经做成一个模板了)
20 Field(i int) StructField
21 // 嵌套获取某个变量信息
22 FieldByIndex(index []int) StructField
23 // 根据变量名获取变量信息
24 FieldByName(name string) (StructField, bool)
25 // 获取变量的个数
26 NumField() int
27 ...
28}
1type Employee struct {
2 id int // 未导出字段
3 Name string // 导出字段
4 Salary float64
5}
6
7func (e Employee) Work() {
8 fmt.Printf("%s is working\n", e.Name)
9}
10
11func (e *Employee) Raise(percent float64) {
12 e.Salary *= (1 + percent/100)
13}
14
15func main() {
16 emp := Employee{1, "Alice", 5000.0}
17 t := reflect.TypeOf(emp)
18
19 // 类型基本信息
20 fmt.Println("Kind:", t.Kind())
21 fmt.Println("Type:", t.String())
22 fmt.Println("Name:", t.Name())
23 fmt.Println("Package:", t.PkgPath())
24
25 // 遍历字段
26 fmt.Println("\nFields:")
27 for i := 0; i < t.NumField(); i++ {
28 field := t.Field(i)
29 fmt.Printf(" %d: %s (type: %v, offset: %v, index: %v)\n",c i, field.Name, field.Type, field.Offset, field.Index)
30 }
31
32 // 遍历方法(注意值接收者和指针接收者的区别)
33 fmt.Println("\nMethods:")
34 for i := 0; i < t.NumMethod(); i++ {
35 method := t.Method(i)
36 fmt.Printf(" %d: %s (type: %v, index: %v)\n", i, method.Name, method.Type, method.Index)
37 }
38
39 // 指针类型的方法
40 pt := reflect.TypeOf(&emp)
41 fmt.Println("\nPointer Methods:")
42 for i := 0; i < pt.NumMethod(); i++ {
43 method := pt.Method(i)
44 fmt.Printf(" %d: %s (type: %v, index: %v)\n", i, method.Name, method.Type, method.Index)
45 }
46}
47
48// Kind: struct
49// Type: main.Employee
50// Name: Employee
51// Package: main
52
53// Fields:
54// 0: id (type: int, offset: 0, index: [0])
55// 1: Name (type: string, offset: 8, index: [1])
56// 2: Salary (type: float64, offset: 24, index: [2])
57
58// Methods:
59// 0: Work (type: func(main.Employee), index: 0)
60
61// Pointer Methods:
62// 0: Raise (type: func(*main.Employee, float64), index: 0)
63// 1: Work (type: func(*main.Employee), index: 1)
1type User struct {
2 ID int `json:"id" db:"user_id"`
3 Username string `json:"username" validate:"required"`
4 Password string `json:"-" db:"pwd"` // JSON序列化时忽略
5}
6
7func parseTags() {
8 u := User{}
9 t := reflect.TypeOf(u)
10
11 for i := 0; i < t.NumField(); i++ {
12 field := t.Field(i)
13 fmt.Printf("\nField: %s\n", field.Name)
14
15 if tag, ok := field.Tag.Lookup("json"); ok {
16 fmt.Println(" JSON tag:", tag)
17 }
18
19 if tag, ok := field.Tag.Lookup("db"); ok {
20 fmt.Println(" DB tag:", tag)
21 }
22
23 if tag, ok := field.Tag.Lookup("validate"); ok {
24 fmt.Println(" Validate tag:", tag)
25 }
26 }
27}
28// Field: ID
29// JSON tag: id
30// DB tag: user_id
31
32// Field: Username
33// JSON tag: username
34// Validate tag: required
35
36// Field: Password
37// JSON tag: -
38// DB tag: pwd
Value
1type Value struct {
2 // 对象的 Type
3 typ_ *abi.Type
4
5 // 指向反射对象的指针
6 ptr unsafe.Pointer
7
8 // 标志位,包含元信息
9 flag
10}
flag
字段是一个位掩码,包含以下信息:
- 值的种类(Kind)
- 是否可寻址(CanAddr)
- 是否是方法接收者
- 是否是指针
- 是否是接口
基本操作
类型检查与转换
1func checkType(v interface{}) {
2 t := reflect.TypeOf(v)
3 switch t.Kind() {
4 case reflect.Int:
5 fmt.Println("It's an int")
6 case reflect.String:
7 fmt.Println("It's a string")
8 // 其他类型...
9 }
10}
值操作
1v := reflect.ValueOf(42)
2if v.CanInt() {
3 fmt.Println(v.Int()) // 获取整数值
4}
5
6s := reflect.ValueOf("hello")
7if s.CanSet() { // 注意:直接这样会返回false,需要可寻址的值
8 s.SetString("world")
9}
修改值(必须可寻址)
1x := 1
2v := reflect.ValueOf(&x).Elem() // 获取可寻址的Value
3if v.CanSet() {
4 v.SetInt(2) // 修改x的值为2
5}
6fmt.Println(x) // 输出2
结构体反射
1type User struct {
2 Name string `json:"name"`
3 Age int `json:"age"`
4}
5
6u := User{"Alice", 25}
7t := reflect.TypeOf(u)
8v := reflect.ValueOf(u)
9
10// 遍历结构体字段
11for i := 0; i < t.NumField(); i++ {
12 field := t.Field(i)
13 value := v.Field(i)
14 fmt.Printf("%s (%s) = %v\n", field.Name, field.Type, value.Interface())
15
16 // 读取tag
17 tag := field.Tag.Get("json")
18 fmt.Println("JSON tag:", tag)
19}
方法调用
1type Calculator struct{}
2
3func (c Calculator) Add(a, b int) int {
4 return a + b
5}
6
7func main() {
8 calc := Calculator{}
9 v := reflect.ValueOf(calc)
10 method := v.MethodByName("Add")
11 args := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}
12 result := method.Call(args)
13 fmt.Println(result[0].Int()) // 输出5
14}
注意事项
- 性能开销:反射操作比直接代码调用慢,应避免在性能关键路径上使用
- 类型安全:反射绕过了编译时类型检查,可能导致运行时panic
- 可读性:反射代码通常较难理解和维护
- 使用限制:
- 无法反射未导出的结构体字段(除非使用
unsafe
包) - 某些操作需要值是可寻址的(
CanSet()
返回true)
- 无法反射未导出的结构体字段(除非使用
在大多数情况下,优先考虑接口和类型断言等更简单、更安全的替代方案。
实际应用示例
简易JSON编码器
1func ToJSON(v interface{}) string {
2 var builder strings.Builder
3 builder.WriteString("{")
4
5 val := reflect.ValueOf(v)
6 typ := val.Type()
7
8 for i := 0; i < val.NumField(); i++ {
9 if i > 0 {
10 builder.WriteString(",")
11 }
12 field := typ.Field(i)
13 jsonTag := field.Tag.Get("json")
14 builder.WriteString(fmt.Sprintf(`"%s":`, jsonTag))
15
16 fieldValue := val.Field(i)
17 switch fieldValue.Kind() {
18 case reflect.String:
19 builder.WriteString(fmt.Sprintf(`"%s"`, fieldValue.String()))
20 case reflect.Int:
21 builder.WriteString(fmt.Sprintf("%d", fieldValue.Int()))
22 // 处理其他类型...
23 }
24 }
25
26 builder.WriteString("}")
27 return builder.String()
28}
动态函数调用
1func CallFuncByName(obj interface{}, funcName string, params ...interface{}) []reflect.Value {
2 objValue := reflect.ValueOf(obj)
3 method := objValue.MethodByName(funcName)
4
5 in := make([]reflect.Value, len(params))
6 for i, param := range params {
7 in[i] = reflect.ValueOf(param)
8 }
9
10 return method.Call(in)
11}
性能
- 避免使用反射
- 避免直接使用 FiledByName,该方法使用 for 循环逐个字段查找,查询效率是O(N),在反射内部字段是按顺序存储的,使用 Field 和下标访问效率时 O(1)。可以缓存 Name 和 Index 的映射关系,避免反复查找。