反射
反射(reflection)是程序在运行时检查自身结构的能力。Go 通过 reflect 包提供。常见用途:JSON 序列化、ORM、依赖注入、配置绑定。
Go 谚语:Reflection is never clear. 反射强大但晦涩,能不反射就不反射。
1. 两个核心类型
reflect.Type:类型信息(方法、字段、tag)reflect.Value:值信息(具体值、可修改性)
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
t := reflect.TypeOf(x) // 类型
v := reflect.ValueOf(x) // 值
fmt.Println("类型:", t) // float64
fmt.Println("值: ", v) // 3.14
fmt.Println("Kind:", t.Kind()) // float64(基础类型分类)
// 互相转换
fmt.Println("v.Float() =", v.Float())
fmt.Printf("t == float64? %v\n", t == reflect.TypeOf(0.0))
}ℹ️Kind vs Type
Type是具体类型(如MyStruct)Kind是基础分类(如struct、int、slice)- 同一 Kind 的不同 Type 不能互相赋值
2. 反射结构体
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string \`json:"name" validate:"required"\`
Age int \`json:"age"\`
Email string \`json:"email,omitempty"\`
}
func inspect(u any) {
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)
fmt.Printf("类型:%s,字段数:%d\n", t.Name(), t.NumField())
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf(" %s: type=%s json=%q validate=%q value=%v\n",
f.Name, f.Type, f.Tag.Get("json"),
f.Tag.Get("validate"), v.Field(i))
}
}
func main() {
inspect(User{Name: "Alice", Age: 30, Email: "a@b.com"})
}💡Tag 的格式
\`json:"name" validate:"required"\`f.Tag.Get("json") 取 json:"..." 的值;f.Tag.Lookup 还能判断是否存在。
3. 反射修改值
要修改原对象,必须传指针。
package main
import (
"fmt"
"reflect"
)
func setField(obj any, name string, value any) error {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("必须是 *Struct 指针")
}
f := v.Elem().FieldByName(name)
if !f.IsValid() {
return fmt.Errorf("没有字段 %s", name)
}
if !f.CanSet() {
return fmt.Errorf("字段 %s 不可设置", name)
}
f.Set(reflect.ValueOf(value))
return nil
}
type User struct {
Name string
Age int
}
func main() {
u := User{Name: "Alice", Age: 30}
fmt.Println("改前:", u)
if err := setField(&u, "Age", 31); err != nil {
fmt.Println("错误:", err)
}
fmt.Println("改后:", u)
}⚠️CanSet 的要求
要 Set 成功:
- 必须传指针(让 reflect 拿到可寻址的 value)
- 字段必须是可导出的(首字母大写)
f.CanSet()必须返回 true
4. 反射调用方法
package main
import (
"fmt"
"reflect"
)
type Calc struct{ X, Y int }
func (c Calc) Sum() int { return c.X + c.Y }
func (c *Calc) Set(x, y int) { c.X, c.Y = x, y }
func main() {
c := Calc{X: 3, Y: 4}
v := reflect.ValueOf(c)
// 反射调用 Sum()
sum := v.MethodByName("Sum").Call(nil)
fmt.Println("Sum:", sum[0].Int()) // 7
// 反射调用 Set(x, y),需要 *Calc
pv := reflect.ValueOf(&c)
args := []reflect.Value{
reflect.ValueOf(10),
reflect.ValueOf(20),
}
pv.MethodByName("Set").Call(args)
fmt.Println("改后 c:", c) // {10 20}
fmt.Println("再次 Sum:", c.Sum()) // 30
}5. 反射 slice / map
package main
import (
"fmt"
"reflect"
)
func main() {
// slice
s := []int{1, 2, 3}
sv := reflect.ValueOf(s)
fmt.Println("len:", sv.Len())
sv.Index(1).SetInt(20) // 修改 s[1]
fmt.Println("改后 s:", s)
// map
m := map[string]int{"a": 1, "b": 2}
mv := reflect.ValueOf(m)
for _, k := range mv.MapKeys() {
fmt.Printf(" %s = %d\n", k, mv.MapIndex(k))
}
// 设置 map 值
mv.SetMapIndex(reflect.ValueOf("c"), reflect.ValueOf(3))
fmt.Println("加 c 后:", m)
}6. 实战:通用 JSON→map 转换
package main
import (
"fmt"
"reflect"
"strings"
)
// StructToMap 用 json tag 作为 key
func StructToMap(obj any) map[string]any {
out := make(map[string]any)
v := reflect.ValueOf(obj)
t := v.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
tag := f.Tag.Get("json")
if tag == "" {
tag = strings.ToLower(f.Name)
}
if idx := strings.Index(tag, ","); idx != -1 {
tag = tag[:idx]
}
out[tag] = v.Field(i).Interface()
}
return out
}
type User struct {
Name string \`json:"name"\`
Age int \`json:"age"\`
Email string \`json:"email,omitempty"\`
}
func main() {
u := User{Name: "Alice", Age: 30}
m := StructToMap(u)
for k, v := range m {
fmt.Printf("%s: %v\n", k, v)
}
}ℹ️实际场景
- ✅
encoding/json、gorm、validator等库大量用反射 - ✅ 配置热加载(YAML/JSON → struct)
- ✅ 实现通用 ORM、DTO 转换器
- ❌ 业务热路径别用反射(性能差,比直接调用慢 10-100x)
🎯 练习
// 任务:实现一个简易的 validator
// 规则:检查所有有 validate:"required" 标记的字符串字段非空
// 1. 写 Validate(obj any) []string,返回错误信息列表
// 2. 用反射遍历字段
// 3. 主函数测试:
// - User{Name: "Alice", Email: ""} -> ["Email 不能为空"]
// - User{Name: "", Email: "a@b.com"} -> ["Name 不能为空"]
// - User{Name: "Bob", Email: "c@d"} -> []
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string \`validate:"required"\`
Email string \`validate:"required"\`
}
func Validate(obj any) []string {
var errs []string
v := reflect.ValueOf(obj)
t := v.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.Tag.Get("validate") != "required" {
continue
}
if f.Type.Kind() != reflect.String {
continue
}
if v.Field(i).String() == "" {
errs = append(errs, fmt.Sprintf("%s 不能为空", f.Name))
}
}
return errs
}
func main() {
tests := []User{
{Name: "Alice", Email: ""},
{Name: "", Email: "a@b.com"},
{Name: "Bob", Email: "c@d"},
}
for _, u := range tests {
errs := Validate(u)
fmt.Printf("%+v -> %v\n", u, errs)
}
}小结
- ✅
reflect.TypeOf拿类型;reflect.ValueOf拿值 - ✅
Kind是基础分类(struct/int/slice/...) - ✅ 用
f.Tag.Get("json")取结构体 tag - ✅ 修改值必须传指针 + 字段可导出
- ✅
v.MethodByName("Foo").Call(args)动态调用方法 - ✅ 反射的真正威力:写通用库(JSON、ORM、validator)
- ❌ 业务代码避免反射(性能差 + 难调试)
下一章 Web 服务。