0

0

Golang反射如何做通用拷贝_Go语言对象复制实现

P粉602998670

P粉602998670

发布时间:2026-01-16 17:59:28

|

165人浏览过

|

来源于php中文网

原创

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

golang反射如何做通用拷贝_go语言对象复制实现

反射拷贝必须处理指针和非导出字段

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,再循环 SetMapIndexIndex(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 中间件、序列化循环)中,容易成为瓶颈。

Type Studio
Type Studio

一个视频编辑器,提供自动转录、自动生成字幕、视频翻译等功能

下载

立即学习go语言免费学习笔记(深入)”;

  • 基准测试显示:对含 5 个字段的 struct,反射 deepCopy 比手写方法慢约 40×;含嵌套 map 时差距扩大到 80×
  • 编译期生成代码(如 go:generate + goderive)比运行时反射更稳更快,适合核心模型
  • 如果只是浅拷贝且确定无指针别名风险,用 unsafe.Copy(Go 1.20+)或 bytes.Copy 配合 unsafe.Slice 更直接

真正难的不是写通反射逻辑,而是判断什么时候不该用它——比如字段带 mutex、channel、function 或 unsafe.Pointer,这些类型根本不能也不该被反射拷贝。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

178

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

226

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

338

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

209

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

391

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

196

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

191

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

192

2025.06.17

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

9

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.3万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号