10 反射
Go 反射详解
10.1 反射是什么
反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。
用比喻来说:反射就是程序在运行的时候能够观察并且纠正自己的行为。
本质
反射的本质是程序在运行期探知对象的类型信息和内存结构。
为什么需要反射?
在高级语言(如 Go)中,无法像汇编语言那样直接和内层打交道获取任意信息,只能通过反射来实现此项技能。
《Go 语言圣经》定义
Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。
10.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.Type 和 reflect.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 中的类型和值的机制
- 通过
TypeOf和ValueOf实现
第二定律
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(),递归比较各类型 |
| 深度拷贝 | 递归复制各类型字段,跳过未导出字段 |

浙公网安备 33010602011771号