反射

允许程序在运行时检查、修改自身的结构和行为。反射通过 reflect 包实现,主要处理类型(Type)和值(Value)的运行时操作。

概念

一般情况下,在编写代码的时候,我们自己定义的结构体肯定知道它有什么样的变量和方法,能够进行特定的处理。但是对于程序代码来说,如果传入的是 interface{} 之类的类型,它们并不知道它拥有什么样的变量和方法,因此,这时候我们就可以利用反射窥探它的元数据,获取它的变量和方法,然后根据这些信息进行特定的处理,比如判断是否存在某个变量,如果存在就做什么样的处理。。。

常用场景

  1. 不能明确接口调用哪个函数,需要根据传入的参数在运行时决定。
  2. 不能明确传入函数的参数类型,需要在运行时处理任意对象。

核心类型

  1. reflect.Type:表示 Go 的类型信息(通过 reflect.TypeOf() 获取)

  2. 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}

注意事项

  1. 性能开销:反射操作比直接代码调用慢,应避免在性能关键路径上使用
  2. 类型安全:反射绕过了编译时类型检查,可能导致运行时panic
  3. 可读性:反射代码通常较难理解和维护
  4. 使用限制:
    • 无法反射未导出的结构体字段(除非使用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}

性能

  1. 避免使用反射
  2. 避免直接使用 FiledByName,该方法使用 for 循环逐个字段查找,查询效率是O(N),在反射内部字段是按顺序存储的,使用 Field 和下标访问效率时 O(1)。可以缓存 Name 和 Index 的映射关系,避免反复查找。