跳转至

📖 反射与泛型

学习时间: 约 5-6 小时 | 难度: ⭐⭐⭐⭐ 中高级 | 前置知识: Go基础、接口与类型系统

📚 章节概述

本章深入讲解 Go 的两大高级类型能力:反射(运行时类型内省与操作)和泛型(Go 1.18+ 编译时类型参数化)。反射提供动态灵活性,泛型提供静态类型安全——理解何时使用哪个是高级 Go 开发者的核心能力。

Go反射与泛型能力边界

上图对比了反射与泛型的适用场景,帮助在“动态灵活性”和“静态类型安全”间做权衡。

🎯 学习目标

  • 深入理解反射的三大定律和底层原理
  • 掌握反射读取结构体字段、标签、动态调用方法
  • 理解反射的性能开销及规避策略
  • 掌握泛型函数、泛型类型的完整语法
  • 理解类型约束(anycomparablecmp.Ordered、自定义约束)
  • 学会使用 ~ 近似约束处理底层类型
  • 能在反射与泛型间做正确的取舍决策

📌 反射(Reflection)

1.1 反射三大定律

Go 反射基于 Rob Pike 提出的三条定律,理解它们是掌握反射的基础:

Go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.14

    // 定律一:从接口值到反射对象
    // reflect.TypeOf 和 reflect.ValueOf 接受 interface{} 参数
    t := reflect.TypeOf(x)   // Type: float64
    v := reflect.ValueOf(x)  // Value: 3.14
    fmt.Println("Type:", t)
    fmt.Println("Value:", v)
    fmt.Println("Kind:", t.Kind()) // Kind: float64

    // 定律二:从反射对象到接口值
    // Value.Interface() 恢复为 interface{}
    y := v.Interface().(float64)
    fmt.Println("Recovered:", y) // 3.14

    // 定律三:要修改反射对象,值必须可设置(addressable)
    // 直接修改会 panic:reflect.ValueOf(x) 是副本,不可设置
    // v.SetFloat(2.71)  // panic: reflect.Value.SetFloat using unaddressable value

    // 正确方式:传入指针,使用 Elem() 获取指向的元素
    p := reflect.ValueOf(&x)
    fmt.Println("CanSet (ptr):", p.CanSet())        // false(指针本身不可设置)
    fmt.Println("CanSet (elem):", p.Elem().CanSet()) // true(指针指向的值可设置)
    p.Elem().SetFloat(2.71)
    fmt.Println("Modified:", x) // 2.71
}

核心要点reflect.ValueOf(x) 传递的是值的副本。要修改原始值,必须传入指针 &x,然后调用 .Elem() 获取可设置的 Value。

1.2 Kind vs Type

Kind 表示底层类别(如 structptrslice),Type 表示具体类型(如 main.User*int):

Go
package main

import (
    "fmt"
    "reflect"
)

type UserID int
type User struct {
    Name string
    Age  int
}

func main() {
    var id UserID = 42
    u := User{Name: "Alice", Age: 30}
    s := []int{1, 2, 3}

    // Kind 是底层类别,Type 是具体类型
    fmt.Println(reflect.TypeOf(id).Kind())  // int(底层种类)
    fmt.Println(reflect.TypeOf(id))         // main.UserID(具体类型)

    fmt.Println(reflect.TypeOf(u).Kind())   // struct
    fmt.Println(reflect.TypeOf(u))          // main.User

    fmt.Println(reflect.TypeOf(s).Kind())   // slice
    fmt.Println(reflect.TypeOf(s))          // []int

    // 常用 Kind 判断
    v := reflect.ValueOf(u)
    switch v.Kind() {
    case reflect.Struct:
        fmt.Println("It's a struct with", v.NumField(), "fields")
    case reflect.Slice:
        fmt.Println("It's a slice with", v.Len(), "elements")
    case reflect.Ptr:
        fmt.Println("It's a pointer to", v.Elem().Type())
    }
}

1.3 结构体字段与标签读取

反射在序列化框架(JSON、ORM)中最常见的用途——读取结构体字段和标签:

Go
package main

import (
    "fmt"
    "reflect"
    "strings"
)

type Employee struct {
    Name     string `json:"name" validate:"required"`
    Age      int    `json:"age" validate:"min=18,max=65"`
    Email    string `json:"email,omitempty" validate:"email"`
    password string // 未导出字段
}

// inspectStruct 读取结构体的所有导出字段和标签
func inspectStruct(v any) {
    t := reflect.TypeOf(v)
    val := reflect.ValueOf(v)

    // 如果传入指针,解引用到元素
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        val = val.Elem()
    }

    if t.Kind() != reflect.Struct {
        fmt.Println("Not a struct")
        return
    }

    fmt.Printf("Struct: %s (%d fields)\n", t.Name(), t.NumField())
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := val.Field(i)

        // 未导出字段无法通过反射读取值
        if !field.IsExported() {
            fmt.Printf("  %s: (unexported)\n", field.Name)
            continue
        }

        jsonTag := field.Tag.Get("json")
        validateTag := field.Tag.Get("validate")
        fmt.Printf("  %s: %v (json=%q, validate=%q)\n",
            field.Name, value.Interface(), jsonTag, validateTag)
    }
}

// parseTag 解析形如 "name,omitempty" 的标签
func parseTag(tag string) (name string, omitempty bool) {
    parts := strings.Split(tag, ",")
    name = parts[0]
    for _, opt := range parts[1:] {
        if opt == "omitempty" {
            omitempty = true
        }
    }
    return
}

func main() {
    emp := Employee{Name: "Alice", Age: 30, Email: ""}
    inspectStruct(emp)

    // 解析 JSON 标签
    t := reflect.TypeOf(emp)
    for i := 0; i < t.NumField(); i++ {
        if !t.Field(i).IsExported() {
            continue
        }
        jsonTag := t.Field(i).Tag.Get("json")
        name, omitempty := parseTag(jsonTag)
        fmt.Printf("Field %s → JSON name=%q, omitempty=%v\n",
            t.Field(i).Name, name, omitempty)
    }
}

1.4 动态方法调用

Go
package main

import (
    "fmt"
    "reflect"
)

type Calculator struct{}

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

// callMethod 通过方法名字符串动态调用方法
func callMethod(obj any, methodName string, args ...any) ([]any, error) {
    v := reflect.ValueOf(obj)
    method := v.MethodByName(methodName)
    if !method.IsValid() {
        return nil, fmt.Errorf("method %s not found", methodName)
    }

    // 构造参数
    in := make([]reflect.Value, len(args))
    for i, arg := range args {
        in[i] = reflect.ValueOf(arg)
    }

    // 调用
    results := method.Call(in)

    // 收集返回值
    out := make([]any, len(results))
    for i, r := range results {
        out[i] = r.Interface()
    }
    return out, nil
}

func main() {
    calc := Calculator{}

    result, err := callMethod(calc, "Add", 3, 5)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Add(3, 5) =", result[0]) // 8

    result, _ = callMethod(calc, "Multiply", 4, 6)
    fmt.Println("Multiply(4, 6) =", result[0]) // 24

    _, err = callMethod(calc, "Divide", 10, 2)
    fmt.Println("Error:", err) // method Divide not found
}

1.5 反射性能开销

反射操作比直接操作慢 50-100 倍。以下基准测试演示了差距:

Go
package reflect_test

import (
    "reflect"
    "testing"
)

type Point struct {
    X, Y int
}

// 直接访问
func BenchmarkDirect(b *testing.B) {
    p := Point{X: 1, Y: 2}
    for b.Loop() { // Go 1.24+ 推荐写法
        _ = p.X + p.Y
    }
}

// 反射访问
func BenchmarkReflect(b *testing.B) {
    p := Point{X: 1, Y: 2}
    v := reflect.ValueOf(p)
    for b.Loop() {
        _ = v.Field(0).Int() + v.Field(1).Int()
    }
}

// 缓存 Type 信息后的反射访问
func BenchmarkReflectCached(b *testing.B) {
    p := Point{X: 1, Y: 2}
    t := reflect.TypeOf(p)
    xIdx, _ := t.FieldByName("X")
    yIdx, _ := t.FieldByName("Y")
    _ = xIdx
    _ = yIdx

    v := reflect.ValueOf(p)
    for b.Loop() {
        _ = v.FieldByIndex(xIdx.Index).Int() + v.FieldByIndex(yIdx.Index).Int()
    }
}

优化策略:将 reflect.TypeOf 的结果缓存到包级变量或 sync.Map 中,避免重复调用。ORM 和 JSON 库通常在初始化时缓存所有类型信息。

1.6 实战:简易结构体验证器

Go
package main

import (
    "fmt"
    "reflect"
    "strconv"
    "strings"
)

// Validate 根据 validate 标签检查结构体字段
func Validate(v any) []string {
    var errs []string
    val := reflect.ValueOf(v)
    t := val.Type()

    if t.Kind() == reflect.Ptr {
        val = val.Elem()
        t = t.Elem()
    }

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := val.Field(i)
        tag := field.Tag.Get("validate")
        if tag == "" {
            continue
        }

        rules := strings.Split(tag, ",")
        for _, rule := range rules {
            if rule == "required" {
                if value.IsZero() {
                    errs = append(errs, fmt.Sprintf("%s is required", field.Name))
                }
            }
            if strings.HasPrefix(rule, "min=") {
                minStr := strings.TrimPrefix(rule, "min=")
                minVal, _ := strconv.ParseInt(minStr, 10, 64)
                if value.Kind() == reflect.Int && value.Int() < minVal {
                    errs = append(errs, fmt.Sprintf("%s must be >= %d", field.Name, minVal))
                }
            }
            if strings.HasPrefix(rule, "max=") {
                maxStr := strings.TrimPrefix(rule, "max=")
                maxVal, _ := strconv.ParseInt(maxStr, 10, 64)
                if value.Kind() == reflect.Int && value.Int() > maxVal {
                    errs = append(errs, fmt.Sprintf("%s must be <= %d", field.Name, maxVal))
                }
            }
        }
    }
    return errs
}

type RegisterForm struct {
    Username string `validate:"required"`
    Age      int    `validate:"required,min=18,max=120"`
    Email    string `validate:"required"`
}

func main() {
    form := RegisterForm{Username: "", Age: 15, Email: "alice@example.com"}
    errs := Validate(form)
    for _, e := range errs {
        fmt.Println("Validation error:", e)
    }
    // Output:
    // Validation error: Username is required
    // Validation error: Age must be >= 18
}

📌 泛型(Generics)

Go 1.18 引入泛型(类型参数),是 Go 语言发展中最大的语法变化。

2.1 泛型函数

Go
package main

import "fmt"

// 泛型函数:T 是类型参数,any 是约束(允许任何类型)
func Print[T any](s []T) {
    for _, v := range s {
        fmt.Println(v)
    }
}

// 多类型参数
func Map[T any, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

// Filter 泛型过滤
func Filter[T any](s []T, pred func(T) bool) []T {
    var result []T
    for _, v := range s {
        if pred(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    // 类型推导:编译器自动推断 T = int
    Print([]int{1, 2, 3})
    // 显式指定类型参数
    Print[string]([]string{"a", "b", "c"})

    // Map: []string → []int(字符串长度)
    lengths := Map([]string{"hello", "go", "world"}, func(s string) int {
        return len(s)
    })
    fmt.Println(lengths) // [5 2 5]

    // Filter: 筛选偶数
    evens := Filter([]int{1, 2, 3, 4, 5, 6}, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(evens) // [2 4 6]
}

2.2 类型约束

约束(constraint)定义了类型参数必须满足的条件。Go 提供三个层次的约束:

Go
package main

import (
    "cmp"
    "fmt"
)

// === 内建约束 ===

// any:允许任何类型(等价于 interface{})
func Contains[T comparable](s []T, target T) bool {
    for _, v := range s {
        if v == target {
            return true
        }
    }
    return false
}
// comparable:支持 == 和 != 运算符的类型

// === 标准库约束 ===

// cmp.Ordered:支持 < <= > >= 比较的类型(所有整数、浮点数、字符串)
func Min[T cmp.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

// === 自定义约束 ===

// 使用 ~ 表示"底层类型为...",支持自定义类型
type Number interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
        ~float32 | ~float64
}

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

// 自定义类型(底层类型为 int)
type Score int

func main() {
    fmt.Println(Contains([]int{1, 2, 3}, 2))      // true
    fmt.Println(Contains([]string{"a", "b"}, "c")) // false

    fmt.Println(Min(3, 5))       // 3
    fmt.Println(Min("go", "ab")) // ab

    fmt.Println(Sum([]int{1, 2, 3}))        // 6
    fmt.Println(Sum([]float64{1.1, 2.2}))   // 3.3

    // ~ 约束的威力:自定义类型 Score 的底层类型是 int,匹配 ~int
    scores := []Score{95, 88, 72}
    fmt.Println(Sum(scores)) // 255
    // 如果 Number 没有 ~,这行会编译失败:Score does not satisfy Number
}

~ 的重要性:没有 ~ 前缀时,int 约束只匹配 int 类型本身。加上 ~int 后,所有底层类型为 int 的自定义类型(如 type Score int)也能匹配。这是实际开发中最常见的需求。

2.3 带方法的约束

约束可以同时要求类型集合和方法签名:

Go
package main

import "fmt"

// Stringer 约束:类型必须有 String() 方法
type Stringer interface {
    String() string
}

func PrintAll[T Stringer](items []T) {
    for _, item := range items {
        fmt.Println(item.String())
    }
}

// 组合约束:既要求底层类型,又要求方法
type FormattableInt interface {
    ~int
    String() string
}

type StatusCode int

func (s StatusCode) String() string {
    switch s {
    case 200:
        return "OK"
    case 404:
        return "Not Found"
    default:
        return fmt.Sprintf("Code(%d)", int(s))
    }
}

func FormatCodes[T FormattableInt](codes []T) []string {
    result := make([]string, len(codes))
    for i, c := range codes {
        result[i] = c.String()
    }
    return result
}

func main() {
    codes := []StatusCode{200, 404, 500}
    PrintAll(codes)
    fmt.Println(FormatCodes(codes)) // [OK Not Found Code(500)]
}

2.4 泛型类型

Go
package main

import "fmt"

// 泛型栈
type Stack[T any] struct {
    elements []T
}

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

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

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

func (s *Stack[T]) Len() int {
    return len(s.elements)
}

// 泛型有序集合(利用 comparable 约束)
type Set[T comparable] struct {
    m map[T]struct{}
}

func NewSet[T comparable]() *Set[T] {
    return &Set[T]{m: make(map[T]struct{})}
}

func (s *Set[T]) Add(v T) {
    s.m[v] = struct{}{}
}

func (s *Set[T]) Contains(v T) bool {
    _, ok := s.m[v]
    return ok
}

func (s *Set[T]) Remove(v T) {
    delete(s.m, v)
}

func (s *Set[T]) Len() int {
    return len(s.m)
}

func (s *Set[T]) Slice() []T {
    result := make([]T, 0, len(s.m))
    for k := range s.m {
        result = append(result, k)
    }
    return result
}

func main() {
    // 泛型栈
    var stack Stack[int]
    stack.Push(10)
    stack.Push(20)
    stack.Push(30)
    if v, ok := stack.Pop(); ok {
        fmt.Println("Popped:", v) // 30
    }
    fmt.Println("Len:", stack.Len()) // 2

    // 泛型集合
    s := NewSet[string]()
    s.Add("go")
    s.Add("rust")
    s.Add("go") // 重复添加无效
    fmt.Println(s.Contains("go"))   // true
    fmt.Println(s.Contains("java")) // false
    fmt.Println(s.Len())            // 2
}

2.5 泛型的局限性

理解泛型不能做什么同样重要:

Go
// ❌ 泛型方法不能有自己的类型参数(Go 1.24 仍不支持)
// type Container[T any] struct{ items []T }
// func (c *Container[T]) Map[U any](f func(T) U) []U { ... }  // 编译错误

// ✅ 正确做法:使用包级泛型函数
func MapSlice[T any, U any](items []T, f func(T) U) []U {
    result := make([]U, len(items))
    for i, v := range items {
        result[i] = f(v)
    }
    return result
}

// ❌ 不能在运行时获取类型参数的具体类型(没有泛型特化)
// func Foo[T any]() { fmt.Println(T) }  // 编译错误

// ❌ 不能对基础类型做类型断言
// func Bar[T any](v T) { v.(int) }  // 编译错误
// ✅ 正确做法:转为 any 后断言
func Bar[T any](v T) {
    if n, ok := any(v).(int); ok {
        fmt.Println("It's an int:", n)
    }
}

📌 反射 vs 泛型:选择指南

场景 推荐方案 原因
类型安全的容器(Stack、Queue) 泛型 编译时检查,零运行时开销
处理多种数值类型的算法 泛型 Number 约束比 interface{} 更安全
JSON/YAML 序列化 反射 需要运行时读取结构体标签
ORM 框架(字段映射) 反射 需要运行时处理未知结构体
依赖注入容器 反射 需要动态创建和管理任意类型
通用排序/查找 泛型 cmp.Ordered 约束足够
配置文件到结构体映射 反射 结构体类型在编译时不可知
通用数据处理管道 泛型 Map/Filter/Reduce 模式
插件系统 反射 需要加载未知类型

🎯 练习题

练习 1:泛型最大值函数

Go
package main

import (
    "cmp"
    "fmt"
)

// TODO: 实现泛型的最大值函数
func Max[T cmp.Ordered](a, b T) T {
    // 提示:使用 > 运算符即可,cmp.Ordered 保证了比较能力
}

func main() {
    fmt.Println(Max(1, 2))       // 2
    fmt.Println(Max("a", "b"))   // b
    fmt.Println(Max(3.14, 2.71)) // 3.14
}

练习 2:泛型 Reduce 函数

Go
package main

import "fmt"

// TODO: 实现泛型 Reduce 函数
// Reduce 将切片归约为单个值
// 参数:切片 s、初始值 init、归约函数 f
func Reduce[T any, U any](s []T, init U, f func(U, T) U) U {
    // 提示:遍历 s,用 f 将每个元素累积到 init 中
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println("Sum:", sum) // 15

    words := []string{"hello", " ", "world"}
    sentence := Reduce(words, "", func(acc, w string) string { return acc + w })
    fmt.Println("Sentence:", sentence) // hello world
}

练习 3:反射实现 struct-to-map

Go
package main

import (
    "fmt"
    "reflect"
)

// TODO: 实现 StructToMap,将任意结构体转为 map[string]any
// 要求:
// 1. 只包含导出字段
// 2. 如果字段有 json 标签,用标签名作为 key
// 3. 忽略 json:"-" 的字段
func StructToMap(v any) map[string]any {
    // 提示:使用 reflect.TypeOf/ValueOf,遍历字段
    // 用 field.Tag.Get("json") 获取 JSON 标签
}

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

func main() {
    u := User{Name: "Alice", Age: 30, Password: "secret", Email: ""}
    m := StructToMap(u)
    fmt.Println(m) // map[age:30 email: name:Alice](不含 Password)
}

💡 面试高频题

Q1: 反射的三大定律是什么?性能如何?

: 1. 接口到反射对象reflect.TypeOf(i)reflect.ValueOf(i) 将接口值转为反射对象 2. 反射对象到接口Value.Interface() 将反射对象恢复为 interface{} 3. 修改需可设置:只有通过指针获取的 ValueElem() 后)才能 Set

性能方面,反射操作通常比直接操作慢 50-100 倍,因为需要运行时类型检查、内存分配。优化策略: - 缓存 reflect.Type 结果 - 减少 FieldByName 调用(O(n)),改用 Field(index)(O(1)) - 在初始化阶段预计算字段索引

Q2: ~intint 约束的区别是什么?

Go
type Strict interface{ int }    // 只匹配 int 类型本身
type Approx interface{ ~int }   // 匹配底层类型为 int 的所有类型

type MyID int

func AddStrict[T Strict](a, b T) T { return a + b }
func AddApprox[T Approx](a, b T) T { return a + b }

// AddStrict(MyID(1), MyID(2))  // ❌ MyID 不满足 Strict
// AddApprox(MyID(1), MyID(2))  // ✅ MyID 底层类型是 int,满足 Approx

实际开发中几乎总应使用 ~ 前缀,否则自定义类型无法使用。标准库的 cmp.Ordered 内部也使用 ~

Q3: comparable 约束和 == 运算符的关系?

comparable 是内建约束,包含所有支持 ==!= 的类型: - 基本类型(int, string, float64, bool 等) - 指针、channel、数组(元素可比较时) - 结构体(所有字段可比较时) - 接口类型

包含:slice、map、func(它们不支持 ==)。

Go
// map 的 key 必须是 comparable 的
// 所以泛型 Set 用 [T comparable] 作为约束
type Set[T comparable] map[T]struct{}

Q4: 什么时候用反射,什么时候用泛型?

:选择原则是尽量用泛型,不得已用反射

  • 泛型:在编译时知道类型集合、需要类型安全、关注性能时使用。适合通用数据结构、算法函数。
  • 反射:在编译时完全不知道类型、需要读取 struct tag、需要动态创建值时使用。适合序列化框架、ORM、验证器。

经验法则:如果你发现自己在泛型中做 any(v).(SomeType) 断言,说明应该用反射或普通接口。


✅ 最佳实践

1. 反射使用原则

Go
// ✅ 好:优先使用类型断言,比反射快
func process(i any) {
    switch v := i.(type) {
    case string:
        fmt.Println("string:", v)
    case int:
        fmt.Println("int:", v)
    default:
        // 只在必要时才用反射
        fmt.Println("unknown:", reflect.TypeOf(i))
    }
}

// ✅ 好:缓存反射类型信息
var typeCache sync.Map // map[reflect.Type]cachedInfo

type cachedInfo struct {
    fields []fieldInfo
}

type fieldInfo struct {
    index   int
    name    string
    jsonKey string
}

func getCachedInfo(t reflect.Type) cachedInfo {
    if v, ok := typeCache.Load(t); ok {
        return v.(cachedInfo)
    }
    info := buildCachedInfo(t) // 构建并缓存
    typeCache.Store(t, info)
    return info
}

// ❌ 避免:在热路径中使用 FieldByName
// reflect.Value.FieldByName 每次都做线性搜索

2. 泛型使用原则

Go
// ✅ 好:用 ~ 让约束支持自定义类型
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
        ~float32 | ~float64
}

// ✅ 好:利用标准库约束而非自定义
import "cmp"

func Max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// ❌ 避免:为不必要的场景引入泛型
// func PrintInt[T ~int](v T) { fmt.Println(v) }  // 过度泛化
// func PrintInt(v int) { fmt.Println(v) }          // 简单直接

// ✅ 好:泛型函数内需要类型断言时,先转 any
func IsZero[T comparable](v T) bool {
    var zero T
    return v == zero
}

📋 学习检查清单

  • 理解反射三大定律:接口→反射、反射→接口、可设置性
  • 区分 Kind(底层类别)和 Type(具体类型)
  • 掌握反射读取结构体字段和标签(FieldTag.Get
  • 掌握反射修改值(指针 + Elem() + Set
  • 理解反射性能开销及缓存优化策略
  • 理解泛型函数语法和类型推导
  • 掌握三大约束层次:anycomparablecmp.Ordered
  • 理解 ~ 近似约束的含义和必要性
  • 能定义带方法签名的自定义约束
  • 掌握泛型结构体和方法的写法
  • 理解泛型的局限性(方法不能有类型参数等)
  • 能在反射与泛型之间做正确的取舍决策

上一章: 测试 | 下一章: Web开发