07 golang的reflect和interface详解 - xiaoxin01/Blog GitHub Wiki
本文通过实际示例来介绍interface{}空接口和reflect反射
inteface{}变量保存2个指针,一个指向具体的值,一个指向值的类型,在64位系统中,一个指针变量为64 bit, 8 byte,下面的测试用以验证:
t.Run("test size of interface{}", func(t *testing.T) {
var i interface{}
assert.Equal(t, uintptr(16), unsafe.Sizeof(i))
})
把值类型变量赋值给interface{}变量时,interface{}变量的值指针指向了一个值类型变量的副本,并不是值本身:
t.Run("test assign basic type to interface{}", func(t *testing.T) {
var i interface{} = a
assert.Equal(t, a, i.(int))
i = 2
assert.Equal(t, 1, a)
})
reflect.ValueOf用于获取保存在interface{}中的值,但它返回的是reflect.Value类型,不是interface{}中的值类型,如果想要获取该值,可以通过 Interface 方法。reflect.ValueOf(i interface{})和reflect.Value.Interface()互为逆方法
t.Run("test get value of interface{}", func(t *testing.T) {
var i interface{} = 1
assert.NotEqual(t, 1, reflect.ValueOf(i))
assert.Equal(t, 1, reflect.ValueOf(i).Interface().(int))
})
可以通过 Set 方法来设置保存在 reflect.Value 中的值,但必须是可设置的才可以,否则会 pinic
t.Run("test set value of interface{}", func(t *testing.T) {
a := 1
var i interface{} = a
value := reflect.ValueOf(i)
value.Set(reflect.ValueOf(2))
assert.Equal(t, 2, value.Interface().(int))
})
因为空接口 i 中保存的是变量 a 的值拷贝,所以即使可以修改 i 中保存的值,也无法改变 a 的值,golang 为了避免这种无效改变值,直接 panic。
可以通过 CanSet 方法来判断 reflect.Value 中保存的值是否可以做有效改变,以及通过引用传递来通过指针来改变原有变量的值
t.Run("test set value of interface{}", func(t *testing.T) {
a := 1
var i interface{} = a
value := reflect.ValueOf(i)
assert.Equal(t, false, value.CanSet())
i = &a
value = reflect.ValueOf(i).Elem()
assert.Equal(t, true, value.CanSet())
value.Set(reflect.ValueOf(2))
assert.Equal(t, 2, a)
})
i 现在保存的是变量 a 的指针地址,reflect.ValueOf(i).Elem() 返回变量 a 的指针,可以通过其来设置原有变量的值。另外注意 Set 方法传递的是 reflect.Value 类型
对于 struct 类型,也可以通过同样的方法来修改原有变量的值
type Student struct {
Age int
Name string
}
t.Run("test set value of struct stored in interface{}", func(t *testing.T) {
var i interface{}
s := Student{Name: "Jason"}
assert.Equal(t, "Jason", s.Name)
i = &s
value := reflect.ValueOf(i).Elem()
value.Field(1).Set(reflect.ValueOf("Vincent"))
assert.Equal(t, "Vincent", s.Name)
})
对于slice类型,保存的是数组的指针,所以在赋值给 interface{} 的时候无需取地址即可更改原有数组
t.Run("test set value of slice stored in interface{}", func(t *testing.T) {
var i interface{}
slice := []int{1, 2, 3}
i = slice
value := reflect.ValueOf(i)
value.Interface().([]int)[0] = 4
assert.Equal(t, 4, slice[0])
})
那么,如果想要给 slice append 新的内容呢?实际上,即使传递 slice 变量的引用给 reflect.Value,它也是不可赋值的,因为 slice 本身不保存数组值,它保存的另外一个数组的指针:
type slice struct {
array unsafe.Pointer
len int
cap int
}
根据这个思路,想要给 slice append 新的内容,做法就是生成新的 slice,然后赋值给原有变量:
t.Run("test append value of slice stored in interface{}", func(t *testing.T) {
var i interface{}
slice := []int{1, 2, 3}
i = &slice
value := reflect.ValueOf(i)
assert.Equal(t, false, value.CanSet())
value = reflect.AppendSlice(value.Elem(), reflect.ValueOf([]int{4, 5, 6}))
slice, ok := value.Interface().([]int)
assert.True(t, ok)
assert.Equal(t, 6, len(slice))
})
希望对理解reflect和interface有帮助