第 12 章:反射与泛型

jerry北京市2026年5月9日Go 20 次阅读 约 12 分钟
第 12 章:反射与泛型

理解 reflect 包的核心 API、Go 1.18 泛型语法,以及它们的实际应用场景。


12.1 反射基础

反射允许程序在运行时检查和操作类型信息:

import "reflect"

x := 42
t := reflect.TypeOf(x)   // reflect.Type
v := reflect.ValueOf(x)  // reflect.Value

fmt.Println(t)          // int
fmt.Println(t.Kind())   // int
fmt.Println(v)          // 42
fmt.Println(v.Int())    // 42

reflect.Type 和 reflect.Value

type User struct {
    Name string `json:"name" validate:"required"`
    Age  int    `json:"age"`
}

u := User{Name: "Alice", Age: 30}
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)

// 遍历字段
for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    value := v.Field(i)
    tag := field.Tag.Get("json")
    fmt.Printf("字段: %s, 类型: %s, 值: %v, Tag: %s\n",
        field.Name, field.Type, value, tag)
}
// 字段: Name, 类型: string, 值: Alice, Tag: name
// 字段: Age, 类型: int, 值: 30, Tag: age

通过反射修改值

x := 42
v := reflect.ValueOf(&x).Elem()  // 必须传指针才能修改

if v.CanSet() {
    v.SetInt(100)
}
fmt.Println(x)  // 100

// 修改结构体字段
u := &User{Name: "Alice", Age: 30}
v = reflect.ValueOf(u).Elem()
nameField := v.FieldByName("Name")
if nameField.CanSet() {
    nameField.SetString("Bob")
}
fmt.Println(u.Name)  // Bob

反射调用方法

type Calculator struct{}

func (c Calculator) Add(a, b int) int {
    return a + b
}

calc := Calculator{}
v := reflect.ValueOf(calc)
method := v.MethodByName("Add")

args := []reflect.Value{
    reflect.ValueOf(3),
    reflect.ValueOf(4),
}
result := method.Call(args)
fmt.Println(result[0].Int())  // 7

反射的性能代价

反射比直接调用慢 10-100 倍,应该谨慎使用:

  • 适合:框架、ORM、序列化库等基础设施代码
  • 不适合:性能敏感的热路径

12.2 泛型(Go 1.18+)

基本语法

// 泛型函数
func Max[T int | float64 | string](a, b T) T {
    if a > b {
        return a
    }
    return b
}

fmt.Println(Max(3, 5))       // 5
fmt.Println(Max(3.14, 2.71)) // 3.14
fmt.Println(Max("a", "b"))   // b

类型约束

// 定义约束接口
type Number interface {
    int | int8 | int16 | int32 | int64 |
    float32 | float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// 使用 ~ 支持底层类型
type MyInt int

type Addable interface {
    ~int | ~float64  // ~ 表示底层类型是 int 或 float64 的都可以
}

func Add[T Addable](a, b T) T {
    return a + b
}

var x MyInt = 1
var y MyInt = 2
fmt.Println(Add(x, y))  // 3

内置约束

import "golang.org/x/exp/constraints"

// comparable:支持 == 和 != 的类型
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// any:等价于 interface{}
func PrintAll[T any](items []T) {
    for _, item := range items {
        fmt.Println(item)
    }
}

泛型结构体

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

// 使用
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
v, _ := intStack.Pop()  // 2

strStack := Stack[string]{}
strStack.Push("hello")

泛型实际应用

// 通用的 Map 函数
func Map[T any, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

nums := []int{1, 2, 3, 4}
strs := Map(nums, func(n int) string {
    return fmt.Sprintf("第%d个", n)
})
// ["第1个", "第2个", "第3个", "第4个"]

// 通用的 Filter 函数
func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

evens := Filter(nums, func(n int) bool { return n%2 == 0 })
// [2, 4]

12.3 面试要点

  1. 反射的三大法则?

    • 从接口值到反射对象(TypeOf/ValueOf)
    • 从反射对象到接口值(Interface())
    • 要修改反射对象,值必须可设置(CanSet,需要传指针)
  2. 反射的性能影响?

    • 比直接调用慢 10-100 倍
    • 会导致额外的内存分配
    • 应避免在热路径中使用
  3. 泛型中 ~ 的作用?

    • 匹配底层类型,如 ~int 匹配所有底层类型为 int 的类型
  4. comparable 约束包含哪些类型?

    • 所有支持 ==!= 的类型
    • 包括基本类型、指针、channel、数组(元素可比较时)、结构体(字段都可比较时)
  5. 什么时候用泛型,什么时候用接口?

    • 泛型:类型安全的容器、通用算法
    • 接口:行为抽象、多态

练习

  1. 使用反射实现一个简单的结构体字段验证器(基于 tag)
  2. 实现泛型版本的 MapFilterReduce 函数
  3. 实现一个泛型的 Set 数据结构
  4. 使用反射实现一个简单的 JSON 序列化函数

← 上一章:sync 包与并发安全 | 下一章:测试与性能优化 →

评论

登录 后发表评论

暂无评论