Dig101: dig more, simplified more and know more
我们都知道Go
的struct
里,小写字段是非导出的,即不可从包外部访问。
但非导出字段在外部也并不是没有办法访问,也不是不可以修改。
今天看下reflect
包如何在包外操作非导出字段。
先来看第一个函数NewAt
:
对于结构体,通过其底层地址(指针 p )和类型,返回指向该结构体的一个指针,
该值是可寻址的(addressable
),即可访问该结构体
// reflect/value.go
// NewAt returns a Value representing a pointer to a value of the
// specified type, using p as that pointer.
func NewAt(typ Type, p unsafe.Pointer) Value {
fl := flag(Ptr)
t := typ.(*rtype)
return Value{t.ptrTo(), p, fl}
}
有个这个方法,就可以通过struct
的反射获取非导出字段
比如访问,对于如下含有非导出字段的结构体Example
package testData
type Example struct {
a string
}
便可以通过对结构体eg
取地址的方式,获取其非导出字段a
的内容
这里Elem
是获取其底层数据对象的方式,
如果知道类型,也可显示指定调用,如reflect.value.Interface,reflect.value.Int...
var eg testData.Example
a:=GetStructPtrUnExportedField(&eg, "a").String()
func GetStructPtrUnExportedField(source interface{}, fieldName string) reflect.Value {
// 获取非导出字段反射对象
v := reflect.ValueOf(source).Elem().FieldByName(fieldName)
// 构建指向该字段的可寻址( addressable )反射对象
return reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()
}
这里注意必须要对eg
取地址, 否则会panic
:
panic: reflect: call of reflect.Value.Elem on struct Value
因为reflect.Value.Elem
需要reflect.Value
类型必须是interface
或者ptr
,
这样获取其底层的值才有意义:要么返回interface
底层的值或者ptr
指向的值
其注释如下:
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value {
那可以访问了,如何修改呢?
利用reflect.value.Set
就可以:
上边Elem
获取到的反射值是可修改的(assignable
),突破了非导出字段不能从外部修改的限制
var eg testData.Example
err := SetStructPtrUnExportedStrField(&eg, "a", "test")
func SetStructPtrUnExportedStrField(source interface{}, fieldName string, fieldVal interface{}) (err error) {
v := GetStructPtrUnExportedField(source, fieldName)
rv := reflect.ValueOf(fieldVal)
if v.Kind() != rv.Kind() {
return fmt.Errorf("invalid kind: expected kind %v, got kind: %v", v.Kind(), rv.Kind())
}
// 修改非导出字段值
v.Set(rv)
return nil
}
这里是以反射值来修改非导出字段值,内部类型须一致。修改后内容会直接反应到eg
上
类似的还有指定类型的设置方法如SetString,SetBool...
当然不取地址也是可以访问非导出字段的。
这里用到的第二个函数是New
:
基于指定类型创建一个可以表示该类型的指针
// New returns a Value representing a pointer to a new zero value
// for the specified type. That is, the returned Value's Type is PtrTo(typ).
func New(typ Type) Value {
if typ == nil {
panic("reflect: New(nil)")
}
t := typ.(*rtype)
ptr := unsafe_New(t)
fl := flag(Ptr)
return Value{t.ptrTo(), ptr, fl}
}
具体访问代码如下:
func GetStructUnExportedField(source interface{}, fieldName string) (accessableField, addressableSourceCopy reflect.Value) {
v := reflect.ValueOf(source)
// since source is not a ptr, get an addressable copy of source to modify it later
addressableSourceCopy = reflect.New(v.Type()).Elem()
// make a copy of source
addressableSourceCopy.Set(v)
accessableField = addressableSourceCopy.FieldByName(fieldName)
accessableField = reflect.NewAt(accessableField.Type(), unsafe.Pointer(accessableField.UnsafeAddr())).Elem()
return
}
这样其实是内部构造了一个对该结构其取地址的指针,以满足后续调用Elem
时可寻址!
非取地址的方式访问没有问题,要还想修改就不会反应到原始结构体上了
毕竟是内部重新拷贝了一个结构体进行的操作。
具体操作类似取地址修改的方式,这里不赘述了。
实际使用中,还是通过NewAt
获取可读写的非导出字段更方便一些。
本文代码见 NewbMiao/Dig101-Go
文章首发公众号:newbmiao
推荐阅读:Dig101-Go 系列
1
janxin 2020-06-14 17:59:09 +08:00
应该把 Go 1.15 的变动放进来 23333
|
2
maoxs2 2020-06-14 19:23:37 +08:00 via Android
都特意小写了就是不让你访问修改。真要是有这个需求请直接 issue 或者 pr 利己利民。奇葩需求也可以直接 fork 。
|
3
AmrtaShiva 2020-06-14 19:33:50 +08:00 via iPhone
走非常规路线注定有踩不完的坑 想起了当初对浮点型比较 出现的偶然性失灵....
|
4
useben 2020-06-14 21:12:11 +08:00
有什么意义。。。
|
5
gamexg 2020-06-14 23:56:13 +08:00 via Android
我是直接建立一个同样的结构,成员变量全导出。
直接强制转换类型。 |
6
movistar 2020-06-15 00:15:09 +08:00
反射操作这么常见的功能。。。
|