Go能否通过反射修改接口方法返回的接口类型切片对应原值?
问题结论
在仅能使用b.Children()返回的切片值、不能直接访问b实例内部children字段的前提下,分两种场景:
- 覆盖修改原切片已有索引范围内的存储元素:无需反射即可实现
- 修改原切片的长度、容量,或是追加/删除元素:即便配合反射也无法实现
原理说明
首先明确Go语言的切片传递规则:切片本质是一个包含底层数组指针、长度、容量的结构体,当你把切片作为返回值或者参数传递时,默认是值拷贝这个结构体,拷贝后的新切片和原切片共享同一底层数组空间,直到任意一方发生扩容操作才会断开共享。
对应你给出的代码:
type Base interface { Do() } type Meta interface { Base Children() []Base } type A struct { } func (a *A) Do() { } type B struct { children []Base } func (b *B) Children() []Base { return b.children }
初始化代码:
a := &A{} b := &B{children: []Base{a}}
场景1:修改已有索引位置的元素
这种场景不需要反射就可以直接完成:
你调用children := b.Children()拿到的切片,和b.children共享底层数组,直接修改children[0] = 新的Base实现实例,b.children[0]的值会同步更新,因为你修改的是共享底层数组里的元素值。
场景2:修改原切片的长度/容量、增删元素
这种场景哪怕用反射也做不到:b.Children()返回的是b.children切片头的拷贝,两个切片头是完全独立的变量。你哪怕用反射修改返回切片的长度、容量属性,也只会修改拷贝后的切片头,完全不会影响B结构体内部存储的原切片头的属性。如果对返回切片做append操作,要么底层数组未扩容时修改了底层数组但原切片长度没变看不到新元素,要么扩容后直接和原底层数组断开关联,更不会影响原切片。
内容的提问来源于stack exchange,提问作者Mahoni
相关产品推荐
相关产品推荐

