07 golang的reflect和interface详解 - xiaoxin01/Blog GitHub Wiki

本文通过实际示例来介绍interface{}空接口和reflect反射

interface{}空接口

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.Value和reflect.Type

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有帮助

参考

⚠️ **GitHub.com Fallback** ⚠️