目录

Go 反射(Reflection)

反射是 Go 语言提供的一种在运行时检查和操作程序类型与值的机制。通过 reflect 包,程序可以在运行时获取变量的类型、值、结构体字段和方法等信息,甚至可以在运行时动态调用方法。反射是许多框架和库(如 jsonorm)的底层基础。

reflect.TypeOf 和 reflect.ValueOf

反射从两个核心函数开始:reflect.TypeOf 获取类型信息,reflect.ValueOf 获取值信息。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num int = 42
	var str string = "hello"
	var flag bool = true

	// TypeOf 返回 reflect.Type,表示类型信息
	fmt.Println("Type of num:", reflect.TypeOf(num))   // int
	fmt.Println("Type of str:", reflect.TypeOf(str))   // string
	fmt.Println("Type of flag:", reflect.TypeOf(flag)) // bool

	// ValueOf 返回 reflect.Value,表示值信息
	vNum := reflect.ValueOf(num)
	fmt.Println("Value of num:", vNum)         // 42
	fmt.Println("Kind of num:", vNum.Kind())   // int
	fmt.Println("Type of num:", vNum.Type())   // int

	// ValueOf 的结果可以转回原始类型
	fmt.Println("Back to int:", vNum.Int())    // 42
}

TypeOf 返回 reflect.Type 接口,ValueOf 返回 reflect.Value 结构体。两者配合使用可以完整地描述一个运行时的值。

reflect.Kind vs reflect.Type

Kind 表示底层的基础类型,Type 表示具体的类型(包括自定义类型)。

package main

import (
	"fmt"
	"reflect"
)

type MyInt int

func main() {
	var x MyInt = 100

	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x)

	fmt.Println("Type:", t)       // MyInt(具体类型)
	fmt.Println("Kind:", v.Kind()) // int(底层类型)

	// Kind 是 int,但 Type 是 MyInt
	// 这意味着 Kind() 会"穿透"自定义类型,看到底层类型
}

常见 Kind 值包括:BoolIntStringSliceMapStructPtrFuncChanInterface 等。

反射修改值

通过反射可以修改变量的值,但前提是必须传入指针reflect.Value 提供了 Set 系列方法。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	x := 10

	// 错误:不能修改非指针的 Value
	// v := reflect.ValueOf(x)
	// v.SetInt(20) // panic: reflect: reflect.Value.SetInt using unaddressable value

	// 正确:传入指针
	ptr := reflect.ValueOf(&x)
	v := ptr.Elem() // Elem 获取指针指向的值

	fmt.Println("Before:", x) // 10
	v.SetInt(20)
	fmt.Println("After:", x) // 20

	// 修改字符串
	s := "hello"
	sp := reflect.ValueOf(&s)
	sp.Elem().SetString("world")
	fmt.Println("String after:", s) // world
}

关键规则:要修改值,必须传入指针的 reflect.Value,然后调用 Elem() 获取指针指向的实际值。

反射操作结构体字段

反射可以遍历和修改结构体的字段。

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email"`
}

func main() {
	u := User{Name: "张三", Age: 25, Email: "zhangsan@example.com"}
	v := reflect.ValueOf(u)

	// 遍历结构体字段
	fmt.Println("--- 字段信息 ---")
	for i := 0; i < v.NumField(); i++ {
		field := v.Type().Field(i)
		fmt.Printf("字段名: %-6s 类型: %-8s Tag: %s 值: %v\n",
			field.Name,
			field.Type,
			field.Tag.Get("json"),
			v.Field(i).Interface(),
		)
	}

	// 通过字段名获取值
	nameField := v.FieldByName("Name")
	fmt.Println("\nName 字段:", nameField.String())

	// 修改结构体字段(需要指针)
	vp := reflect.ValueOf(&u)
	vp.Elem().FieldByName("Age").SetInt(30)
	fmt.Println("修改后 Age:", u.Age) // 30
}

NumField() 返回字段数量,Field(i) 按索引获取字段,FieldByName(name) 按名称获取字段。Type().Field(i) 返回 StructField,包含字段名、类型和 tag 信息。

反射调用方法

反射可以在运行时动态调用方法。

package main

import (
	"fmt"
	"reflect"
)

type Calculator struct {
	Value int
}

func (c Calculator) Add(n int) int {
	return c.Value + n
}

func (c Calculator) Multiply(n int) int {
	return c.Value * n
}

func (c *Calculator) SetValue(n int) {
	c.Value = n
}

func main() {
	c := Calculator{Value: 10}
	v := reflect.ValueOf(c)

	// 获取方法
	addMethod := v.MethodByName("Add")
	fmt.Println("Add 方法存在:", addMethod.IsValid())

	// 调用方法(参数为 []reflect.Value)
	args := []reflect.Value{reflect.ValueOf(5)}
	result := addMethod.Call(args)
	fmt.Println("Add(5) =", result[0].Int()) // 15

	// 调用指针方法需要传入指针的 Value
	vp := reflect.ValueOf(&c)
	setMethod := vp.MethodByName("SetValue")
	setMethod.Call([]reflect.Value{reflect.ValueOf(100)})
	fmt.Println("SetValue(100) 后 Value =", c.Value) // 100
}

MethodByName 获取方法,Call 传入 []reflect.Value 类型的参数切片,返回 []reflect.Value 类型的结果。注意:调用指针接收者方法时,必须传入指针的 reflect.Value

反射的性能开销

反射涉及大量的类型检查和动态分发,性能远低于直接调用。

package main

import (
	"fmt"
	"reflect"
	"testing"
)

type Data struct {
	X int
	Y string
}

func directAccess() {
	d := Data{X: 1, Y: "hello"}
	_ = d.X
	_ = d.Y
}

func reflectAccess() {
	d := Data{X: 1, Y: "hello"}
	v := reflect.ValueOf(d)
	_ = v.FieldByName("X").Int()
	_ = v.FieldByName("Y").String()
}

func main() {
	// 使用 testing 包进行基准测试
	resultDirect := testing.Benchmark(func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			directAccess()
		}
	})

	resultReflect := testing.Benchmark(func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			reflectAccess()
		}
	})

	fmt.Printf("直接访问: %v/op\n", resultDirect.NsPerOp())
	fmt.Printf("反射访问: %v/op\n", resultReflect.NsPerOp())
	fmt.Printf("反射比直接访问慢约 %d 倍\n",
		resultReflect.NsPerOp()/resultDirect.NsPerOp())
}

反射通常比直接访问慢 10-100 倍,在性能敏感的代码路径中应避免使用反射。

反射的最佳实践

1. 尽量少用反射

反射使代码难以阅读和维护,且性能较差。能用类型系统解决的问题不要用反射。

2. 处理 panic

反射中的非法操作会导致 panic,应使用 recover 捕获。

package main

import (
	"fmt"
	"reflect"
)

func safeFieldByName(v reflect.Value, name string) (reflect.Value, bool) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("panic recovered: %v\n", r)
		}
	}()

	field := v.FieldByName(name)
	if !field.IsValid() {
		return reflect.Value{}, false
	}
	return field, true
}

func main() {
	type S struct{ X int }
	s := S{X: 42}
	v := reflect.ValueOf(s)

	if f, ok := safeFieldByName(v, "X"); ok {
		fmt.Println("Field X:", f.Int())
	}

	if _, ok := safeFieldByName(v, "Y"); !ok {
		fmt.Println("Field Y does not exist")
	}
}

3. 使用 reflect.Value 的 CanSet 检查

在修改值之前,先检查是否可以设置。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	x := 10
	v := reflect.ValueOf(x)
	fmt.Println("CanSet (value):", v.CanSet()) // false

	vp := reflect.ValueOf(&x)
	fmt.Println("CanSet (pointer):", vp.CanSet()) // false(指针本身不可设置)
	fmt.Println("CanSet (elem):", vp.Elem().CanSet()) // true
}

总结

反射提供了在运行时检查和操作程序的能力,核心是 reflect.TypeOfreflect.ValueOfKind 表示底层类型,Type 表示具体类型。通过反射可以修改值(需传入指针)、遍历结构体字段和 tag、动态调用方法。但反射性能开销大、代码可读性差,应在必要时才使用,如编写通用框架、序列化库或 ORM 工具。日常业务代码应优先使用类型安全的方式实现功能。