10 反射

Go 反射详解

10.1 反射是什么

反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。

用比喻来说:反射就是程序在运行的时候能够观察并且纠正自己的行为。

本质

反射的本质是程序在运行期探知对象的类型信息和内存结构

为什么需要反射?

在高级语言(如 Go)中,无法像汇编语言那样直接和内层打交道获取任意信息,只能通过反射来实现此项技能。

《Go 语言圣经》定义

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。


10.2 什么情况下需要使用反射

使用反射的常见场景

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

不推荐使用反射的原因

问题 说明
可读性差 反射相关代码难以阅读,而代码可读性是软件工程的重要指标
类型安全缺失 编译器无法提前发现类型错误,可能运行很久后才 panic,造成严重后果
性能损耗 比正常代码运行速度慢一到两个数量级,应避免在性能关键位置使用

10.3 Go 语言如何实现反射

核心原理

  • 接口(interface)是 Go 语言实现反射的基础
  • 当向接口变量赋予一个实体类型时,接口会存储实体的类型信息
  • 反射就是通过接口的类型信息实现的
  • Go 在 reflect 包里定义了各种类型和函数,用于在运行时检测类型信息、改变类型的值

10.3.1 types 和 interface

静态类型 vs 动态类型

每个变量都有一个静态类型,在编译阶段确定:

type MyInt int
var i int    // 静态类型:int
var j MyInt  // 静态类型:MyInt

尽管 i 和 j 的底层类型都是 int,但它们是不同的静态类型,不能直接赋值。

接口的底层结构

非空接口(iface)

// src/runtime/runtime2.go
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype
    _type *_type
    link  *itab
    hash  uint32
    _     [4]byte
    fun   [1]uintptr
}

空接口(eface)

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

接口转换示例

var r io.Reader
tty, _ := os.OpenFile("/Users/qcrao/test", os.O_RDWR, 0)
r = tty
  • r 的静态类型:io.Reader
  • r 的动态类型:*os.File
  • r 的动态值:tty(打开的文件对象)

断言转换

var w io.Writer
w = r.(io.Writer)  // 断言成功,因为 *os.File 也实现了 Write 方法

空接口赋值

var empty interface{}
empty = w  // 所有类型都实现了空接口,无需断言

接口内存结构图解

变量 静态类型 动态类型 可调用的方法
r io.Reader *os.File Read()
w io.Writer *os.File Write()
empty interface{} *os.File

10.3.2 反射的基本函数

reflect 包定义了 reflect.Typereflect.Value 两个核心类型。

reflect.TypeOf

func TypeOf(i interface{}) Type
  • 提取接口中值的类型信息
  • 实参会先被转化为 interface{} 类型
  • 返回值 Type 是一个接口,定义了获取类型信息的方法

源码实现

func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

reflect.ValueOf

func ValueOf(i interface{}) Value
  • 返回 interface{} 里存储的实际变量
  • 返回值 Value 是一个结构体,包含类型信息和实际值
  • 可以通过 Value 的方法操作实际数据

源码实现

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
    return unpackEface(i)
}

Type 接口的方法(部分)

方法 说明
Align() 内存对齐策略
Method(i int) 返回第 i 个方法
Name() 类型名称
Size() 类型大小
Kind() 类型值(如 struct、int 等)
Implements(u Type) 是否实现接口 u
Elem() 返回内部子元素类型(用于 Array、Chan、Map、Ptr、Slice)
Field(i int) 返回结构体第 i 个字段
NumField() 返回结构体字段数量
NumMethod() 返回方法数量

Value 结构体的方法(部分)

方法 说明
SetLen(n int) 设置切片长度
SetCap(n int) 设置切片容量
SetMapIndex(key, val Value) 设置字典键值对
Index(i int) 返回索引 i 处的值
FieldByName(name string) 根据名称获取结构体字段
Int() int64 获取 int 类型的值
Call(in []Value) []Value 调用函数或方法
CanSet() bool 判断是否可设置
Elem() Value 返回指针指向的元素

Type、Value 与 Interface 的转换关系

interface{} ←→ Type (通过 TypeOf)
interface{} ←→ Value (通过 ValueOf)
Value ←→ Type (通过 Value.Type())
Value ←→ interface{} (通过 Value.Interface())

10.3.3 反射的三大定律

第一定律

Reflection goes from interface value to reflection object.

反射从接口值到反射对象。

  • 反射是一种检测存储在 interface 中的类型和值的机制
  • 通过 TypeOfValueOf 实现

第二定律

Reflection goes from reflection object to interface value.

反射从反射对象到接口值。

  • 与第一定律相反
  • 通过 Value.Interface() 方法将反射对象还原为接口变量

第三定律

To modify a reflection object, the value must be settable.

要修改反射对象,该值必须是可设置的。

错误示例

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // panic! 不可设置

原因:ValueOf(x) 传递的是 x 的副本,v 代表的是副本,不是原变量。

正确做法:使用指针

var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()  // v 代表 x 本身
v.SetFloat(7.1)
fmt.Println(x) // 7.1

关键:反射变量 Value 必须持有原变量的地址才能修改原变量。


10.4 如何比较两个对象是否完全相同

reflect.DeepEqual

func DeepEqual(x, y interface{}) bool

比较两个变量是否"深度"相等。

重要规则

不同类型的变量,即使底层类型相同、值相同,也不深度相等

type MyInt int
type YourInt int

func main() {
    m := MyInt(1)
    y := YourInt(1)
    fmt.Println(reflect.DeepEqual(m, y)) // false
}

深度相等的判定规则

类型 深度相等条件
Array 相同索引处的元素"深度"相等
Struct 相应字段(包含导出和不导出)"深度"相等
Func 两者都是 nil
Interface 两者存储的具体值"深度"相等
Map 都为 nil;或非空、长度相等,指向同一个 map 实体对象,或相应 key 指向的 value "深度"相等
Pointer 使用 == 比较相等;或指向的实体"深度"相等
Slice 都为 nil;或非空、长度相等,首元素指向同一个底层数组的相同元素,或相同索引处的元素"深度"相等
numbers, bools, strings, channels 使用 == 比较为真

特殊情况

  • func 类型:不可比较,只有两者都是 nil 时才深度相等
  • float 类型:精度原因,不能直接使用 == 比较
  • 循环引用:通过 visited map 记录已比较的对象,避免无限循环

源码核心逻辑

func DeepEqual(x, y interface{}) bool {
    if x == nil || y == nil {
        return x == y
    }
    v1 := ValueOf(x)
    v2 := ValueOf(y)
    if v1.Type() != v2.Type() {
        return false
    }
    return deepValueEqual(v1, v2, make(map[visit]bool), 0)
}

10.5 如何利用反射实现深度拷贝

深度拷贝 vs 浅拷贝

拷贝类型 说明
浅拷贝 只复制指向对象的指针,新旧对象共享同一块内存
深度拷贝 创建内容完全相同的对象,新旧对象不共享内存,修改新对象不影响原对象

简单实现:json.Marshal/Unmarshal

func Copy(dst interface{}, src interface{}) error {
    if dst == nil || src == nil {
        return fmt.Errorf("nil src or dst")
    }
    bytes, err := json.Marshal(src)
    if err != nil {
        return fmt.Errorf("Unable to serialize src: %s", err)
    }
    err = json.Unmarshal(bytes, dst)
    if err != nil {
        return fmt.Errorf("Unable to deserialize into dst: %s", err)
    }
    return nil
}

缺点

  • 性能消耗大(序列化/反序列化)
  • 无法复制未导出的私有字段

反射实现深度拷贝

主函数

func Copy(src interface{}) interface{} {
    if src == nil {
        return nil
    }
    original := reflect.ValueOf(src)
    cpy := reflect.New(original.Type()).Elem()
    copyRecursive(original, cpy)
    return cpy.Interface()
}

递归复制函数

func copyRecursive(original, cpy reflect.Value) {
    switch original.Kind() {
    case reflect.Ptr:
        // 复制指针指向的对象,而不是指针本身
        originalValue := original.Elem()
        if !originalValue.IsValid() {
            return
        }
        cpy.Set(reflect.New(originalValue.Type()))
        copyRecursive(originalValue, cpy.Elem())
        
    case reflect.Interface:
        // 先反射接口字段包含的值,再递归复制
        if original.IsNil() {
            return
        }
        originalValue := original.Elem()
        copyValue := reflect.New(originalValue.Type()).Elem()
        copyRecursive(originalValue, copyValue)
        cpy.Set(copyValue)
        
    case reflect.Struct:
        // 特殊处理 time.Time
        if t, ok := original.Interface().(time.Time); ok {
            cpy.Set(reflect.ValueOf(t))
            return
        }
        // 递归复制每个字段,跳过未导出的字段
        for i := 0; i < original.NumField(); i++ {
            if original.Type().Field(i).PkgPath != "" {
                continue  // 跳过未导出字段
            }
            copyRecursive(original.Field(i), cpy.Field(i))
        }
        
    case reflect.Slice:
        // 复制所有元素
        if original.IsNil() {
            return
        }
        cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
        for i := 0; i < original.Len(); i++ {
            copyRecursive(original.Index(i), cpy.Index(i))
        }
        
    case reflect.Map:
        // 复制所有键值对
        if original.IsNil() {
            return
        }
        cpy.Set(reflect.MakeMap(original.Type()))
        for _, key := range original.MapKeys() {
            originalValue := original.MapIndex(key)
            copyValue := reflect.New(originalValue.Type()).Elem()
            copyRecursive(originalValue, copyValue)
            copyKey := Copy(key.Interface())
            cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
        }
        
    default:
        // 基础类型,直接复制
        cpy.Set(original)
    }
}

反射的常见应用场景

  • IDE 代码自动补全功能
  • 对象序列化(encoding/json)
  • 深度拷贝
  • fmt 相关函数的实现
  • 对象关系映射(ORM)
  • 一切需要对值进行断言的需求

总结

知识点 要点
反射定义 运行时访问、检测、修改程序状态和行为的能力
使用场景 运行时决定调用函数、处理任意类型参数
缺点 可读性差、类型不安全、性能损耗大
核心函数 reflect.TypeOf()reflect.ValueOf()
三大定律 接口→反射对象、反射对象→接口、可设置性
深度比较 reflect.DeepEqual(),递归比较各类型
深度拷贝 递归复制各类型字段,跳过未导出字段
posted @ 2026-03-30 15:29  cyusouyiku  阅读(2)  评论(0)    收藏  举报