deepCopy 函数需分别处理指针、切片、映射和接口类型:指针需解引用后递归拷贝并新建指针;切片需创建新底层数组并逐元素拷贝;映射需新建并逐键值对递归拷贝;接口需先 Elem() 获取内部值,再判空避免 panic。

反射拷贝必须处理指针和非导出字段
Go 的 reflect 包默认跳过非导出(小写开头)字段,且对指针类型不做自动解引用。直接用 reflect.Copy 或遍历 reflect.Value 字段复制时,若源是 *T、目标是 T,或结构体含私有字段,会静默失败或 panic。
- 确保源值已通过
reflect.Indirect解引用到可读取的实际值,避免panic: reflect: call of reflect.Value.Interface on zero Value - 目标值必须可寻址(
reflect.Value.Addr()可调用),否则无法写入;常见错误是传入字面量或只读副本 - 非导出字段需用
reflect.StructField.IsExported()显式跳过,不能依赖CanInterface()判断——它在字段不可导出时直接返回 false,但不报错
deepCopy 函数要区分 slice/map/interface{} 类型
通用拷贝不是简单递归调用 reflect.Copy。不同复合类型的复制逻辑差异大:slice 需新建底层数组,map 要重新 make 并逐键赋值,interface{} 值需先取出具体类型再处理。
- 对
reflect.Slice:用reflect.MakeSlice(t, v.Len(), v.Cap())创建新 slice,再循环SetMapIndex或Index(i).Set() - 对
reflect.Map:必须先reflect.MakeMapWithSize(t, v.Len()),再遍历v.MapKeys(),对每个 key/value 递归 deepCopy 后SetMapIndex - 对
reflect.Interface:用v.Elem()获取内部值,再判断其 kind 是否为reflect.Invalid(nil interface),避免 panic
func deepCopy(v reflect.Value) reflect.Value {
if !v.IsValid() {
return v
}
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
return reflect.Zero(v.Type())
}
unpacked := reflect.New(v.Elem().Type())
unpacked.Elem().Set(deepCopy(v.Elem()))
return unpacked
case reflect.Slice:
if v.IsNil() {
return reflect.Zero(v.Type())
}
newSlice := reflect.MakeSlice(v.Type(), v.Len(), v.Cap())
for i := 0; i < v.Len(); i++ {
newSlice.Index(i).Set(deepCopy(v.Index(i)))
}
return newSlice
case reflect.Map:
if v.IsNil() {
return reflect.Zero(v.Type())
}
newMap := reflect.MakeMapWithSize(v.Type(), v.Len())
for _, key := range v.MapKeys() {
newMap.SetMapIndex(key, deepCopy(v.MapIndex(key)))
}
return newMap
default:
if v.CanInterface() {
return reflect.ValueOf(v.Interface())
}
return reflect.Zero(v.Type())
}
}
struct 字段 tag 控制拷贝行为很实用
生产环境里常需要跳过某些字段(如数据库 ID、时间戳),或按需深拷贝(如嵌套 struct 是否展开)。靠反射自动推断不够可靠,应支持结构体字段 tag,例如 json:"-" 或自定义 copy:"-" / copy:"shallow"。
- 在遍历 struct 字段前,先检查
sf.Tag.Get("copy"),若为"-"则跳过该字段 - 注意 tag 解析要 fallback 到空字符串,避免
Get返回空导致误判;不要用sf.Tag != ""判断是否有 tag - 如果字段类型是 struct 且 tag 为
"shallow",则直接Set而非递归deepCopy,减少开销
性能敏感场景别无脑用反射拷贝
反射拷贝比手写 Clone() 方法慢 10–100 倍,GC 压力也更大。尤其在高频调用(如 HTTP 中间件、序列化循环)中,容易成为瓶颈。
立即学习“go语言免费学习笔记(深入)”;
- 基准测试显示:对含 5 个字段的 struct,反射 deepCopy 比手写方法慢约 40×;含嵌套 map 时差距扩大到 80×
- 编译期生成代码(如
go:generate+goderive)比运行时反射更稳更快,适合核心模型 - 如果只是浅拷贝且确定无指针别名风险,用
unsafe.Copy(Go 1.20+)或bytes.Copy配合unsafe.Slice更直接
真正难的不是写通反射逻辑,而是判断什么时候不该用它——比如字段带 mutex、channel、function 或 unsafe.Pointer,这些类型根本不能也不该被反射拷贝。










