【Go反射】创建对象
前言
最近在写一个自动配置的库cfgm,其中序列化和反序列化的过程用到了大量反射,主要部分写完之后,我在这里回顾总结一下反射的基本操作。
第一篇【Go反射】读取对象中总结了利用反射读取对象的方法。
第二篇【Go反射】修改对象中总结了利用反射修改对象的方法。
本篇总结一下创建操作,即创建新的简单类型(int、uint、float、bool、string)、指针、切片、数组、map、结构体对象。
先声明一下后续代码中需要引入的包:
import (
"github.com/stretchr/testify/assert"
"reflect"
"testing"
)
参考
目录
reflect.New()
利用反射创建对象其实十分简单,reflect.New()
提供了类似内置函数new()
的功能:
new()
返回指定类型的指针,该指针指向新创建的对象;reflect.New()
返回指定类型指针的反射对象(Value结构体),该结构体解引用即可过的新创建的对象的反射对象;
举个例子:
func TestCreateSimple(t *testing.T) {
typ := reflect.TypeOf(int(0))
ptrValue := reflect.New(typ)
integerValue := ptrValue.Elem() // 一定不要忘记
integerValue.SetInt(123)
assert.Equal(t, 123, integerValue.Interface().(int))
}
它其实相当于:
func TestCreateSimple_WithoutReflect(t *testing.T) {
ptr := new(int)
*ptr = 123
assert.Equal(t, 123, *ptr)
}
只不过后者需要一个硬编码的int
传入new()
,而利用反射可以通过一个Type
对象(其实是个接口)来创建任何给定的对象。
reflect.New()
选择返回一个指针的反射对象,而不是直接返回目标对象的反射对象,一方面是为了和内置函数new()
的行为相统一,另一方面通过返回指针然后进行解引用的操作,使得刚刚被创建的对象是addressable
的,也就意味着它是可修改的(有关这一部分,请参考上一篇)。
我们可以轻松地通过reflect.New
反射创建任何类型的对象,但是其前提是获得需要创建的对象的Type
,这也是主要的难点所在。
通过基础类型构造复杂类型
Go的reflect
库提供了很多方法,能够通过基础类型的Type
对象,创建出复杂类型的Type
对象,从而创建出复杂类型的反射对象(Value结构体)。
创建指针Type
func TestCreatePtr_FromBase(t *testing.T) {
typ := reflect.TypeOf(int(0))
ptrType := reflect.PtrTo(typ)
ptrValue := reflect.New(ptrType).Elem()
assert.Equal(t, (*int)(nil), ptrValue.Interface().(*int))
}
利用reflect.PtrTo()
,通过一个int
的Type
构建了一个*int
的Type
,进而创建了一个*int
指针对象,注意创建的指针为nil指针。
创建数组Type
func TestCreateArray_FromBase(t *testing.T) {
typ := reflect.TypeOf(int(0))
arrType := reflect.ArrayOf(4, typ)
arrValue := reflect.New(arrType).Elem()
assert.Equal(t, [4]int{}, arrValue.Interface().([4]int))
}
长度是一个数组的类型的一部分,通过基类型构造数组类型时,需要指定数组长度。
创建的数组中的每一个元素都是默认初始化的。
创建切片Type
func TestCreateSlice_FromBase(t *testing.T) {
typ := reflect.TypeOf(int(0))
sliceType := reflect.SliceOf(typ)
sliceValue := reflect.New(sliceType).Elem()
assert.Equal(t, *new([]int), sliceValue.Interface().([]int))
}
注意创建的切片是一个nil切片,而不是一个长度为0的切片。还需要通过.Set()
和reflect.MakeSlice()
的配合来进行进一步初始化(后文再讨论)。
创建mapType
func TestCreateMap_FromBase(t *testing.T) {
ktyp := reflect.TypeOf(int(0))
vtyp := reflect.TypeOf("")
mapType := reflect.MapOf(ktyp, vtyp)
mapValue := reflect.New(mapType).Elem()
assert.Equal(t, *new(map[int]string), mapValue.Interface().(map[int]string))
}
reflect.MapOf()
接受两个Type
,分别是map的key和value的Type
。
和切片一样,创建的map是一个nil的map,而不是容量为0的map,还需要进行进一步初始化。
小结
记typ
为int
、uint
之类的字面类型,记Typ
为typ
对应的反射类型,即Typ := reflect.TypeOf(typ(0))
目标Type | 函数 |
---|---|
*typ | reflect.PtrTo(Typ) |
[...]typ | reflect.ArrayOf(Typ) |
[]typ | reflect.SliceOf(Typ) |
map[typ1]typ2 | reflect.MapOf(Typ1, Typ2) |
chan typ | reflect.ChanOf(Typ) |
reflect.MakeXXX()
reflect
包提供了一系列MakeXXX()
的方法,对应内置函数make()
。
make创建切片
func TestCreateSlice_Make(t *testing.T) {
typ := reflect.TypeOf(int(0))
sliceType := reflect.SliceOf(typ)
makeValue := reflect.MakeSlice(sliceType, 2, 4)
makeValue.Index(1).SetInt(1)
assert.False(t, makeValue.CanSet())
assert.Equal(t, []int{0, 1}, makeValue.Interface().([]int))
assert.Equal(t, 4, cap(makeValue.Interface().([]int)))
}
reflect.MakeSlice()
类似于make()
,是实际创建切片的函数。
不过需要注意的是,reflect.MakeSlice()
的返回值并不是一个addressable
的切片反射对象。
虽然切片的元素是addressable
的,我们仍旧可以直接更改切片内的元素,但是切片的长度、容量我们无法直接更改。
因此,如果我们在MakeSlice()
之后还需要更改切片的长度和容量,就还是需要先通过.Set()
将其赋值给一个addressable
的对象再进行修改,但我实在想象不出什么情况下需要这么干,因为可以在reflect.MakeSlice()
的时候进行指定。
make创建map
func TestCreateMap_Make(t *testing.T) {
ktyp := reflect.TypeOf("")
vtyp := reflect.TypeOf(int(0))
mapType := reflect.MapOf(ktyp, vtyp)
dictValue := reflect.MakeMap(mapType)
dictValue.SetMapIndex(reflect.ValueOf("A"), reflect.ValueOf(1))
assert.False(t, dictValue.CanAddr())
assert.Equal(t, map[string]int{"A":1}, dictValue.Interface().(map[string]int))
}
reflect.MakeMap()
和reflect.MakeSlice()
类似,创建的都是一个非addressable
的反射对象。不过.SetMapIndex()
并不要求map反射对象是addressable
的,所以也无伤大雅。
其它创建对象的方法
reflect.Zero()
这个函数用于创建零值,和reflect.New()
不同,后者虽然创建的新对象也是零值,但是通过解引用可以获得一个addressable
的反射对象,而reflect.Zero()
返回的则是一个.CanAddr()
为false
的反射对象。从源码上来看,所有通过reflect.Zero()
创建的小对象都是公用同一块被置零的空间的。
reflect.Zero()
大部分时候是配合Value结构体的.Set()
方法来使用的。下面通过将指针置为nil来进行示例:
func TestCreatePtr_Nil(t *testing.T) {
var integer int = 1
ptr := &integer
ptrValue := reflect.ValueOf(&ptr).Elem()
ptrValue.Set(reflect.Zero(ptrValue.Type()))
assert.Equal(t, (*int)(nil), ptr)
}
reflect.Append()
这个函数模拟了内置函数append
func TestCreateSlice_Append(t *testing.T) {
slice := []int{1, 2, 3}
sliceValue := reflect.ValueOf(slice)
elemValue := reflect.ValueOf(int(4))
appendSlice := reflect.Append(sliceValue, elemValue)
assert.False(t, appendSlice.CanAddr())
assert.Equal(t, []int{1, 2, 3, 4}, appendSlice.Interface().([]int))
}
reflect.Append()
传入一个切片的反射对象和一个追加的元素的反射对象,返回一个新的反射切片反射对象。传入的反射对象不要求是addressable
的,返回的反射对象也不是addressable
的。
不过有时我们需要模拟slice = append(slice, elem)
,这个时候就需要切片的反射对象是addressable
的(因为需要对它调用.Set()
):
func TestSetSlice_Append(t *testing.T) {
slice := []int{1, 2, 3}
sliceValue := reflect.ValueOf(&slice).Elem()
elemValue := reflect.ValueOf(int(4))
sliceValue.Set(reflect.Append(sliceValue, elemValue))
assert.Equal(t, []int{1, 2, 3, 4}, slice)
}
reflect.AppendSlice()
对应于append(slice1, slice2...)
,reflect
包提供了reflect.AppendSlice()
:
func TestCreateSlice_AppendSlice(t *testing.T) {
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
slice1Value := reflect.ValueOf(slice1)
slice2Value := reflect.ValueOf(slice2)
appendSlice := reflect.AppendSlice(slice1Value, slice2Value)
assert.False(t, appendSlice.CanAddr())
assert.Equal(t, []int{1, 2, 3, 4, 5, 6}, appendSlice.Interface().([]int))
}
slice1 = append(slice1, slice2...)
的效果同理reflect.Append()
,这里不多赘述。
.Slice() 和 .Slice3()
func TestCreatSlice_Slice3(t *testing.T) {
slice := []int{1, 2, 3, 4}
sliceValue := reflect.ValueOf(slice)
newSlice := sliceValue.Slice3(1, 3, 3)
assert.False(t, newSlice.CanAddr())
assert.Equal(t, []int{2, 3}, newSlice.Interface().([]int))
assert.Equal(t, 2, cap(newSlice.Interface().([]int)))
}
对一个切片的反射对象或者对一个数组的反射对象调用.Slice(i, j)
来模拟slice := array[i:j]
,调用.Slice(i, j, k)
来模拟slice := array[i:j:k]
。
需要注意的是,如果是对切片的反射对象调用,并不要求该反射对象是addressable
的,但是如果对数组调用,则需要时addressable
的:
func TestCreatSlice_Slice(t *testing.T) {
array := [...]int{1, 2, 3, 4}
arrayValue := reflect.ValueOf(&array).Elem()
newSlice := arrayValue.Slice(1, 3)
assert.False(t, newSlice.CanAddr())
assert.Equal(t, []int{2, 3}, newSlice.Interface().([]int))
}
和reflect.MakeXXX()
以及reflect.Append()
、reflect.AppendSlice()
类似,通过.Slice()
和.Slice3()
创建的切片的Value结构体也不是addressable
的。
总结
本文介绍了通过reflect.New()
和reflect
包中各种构造复杂Type的方法来创建对象的方法,并介绍了一系列能够产生新的非addressable
对象的方法。
转载请注明原文地址:https://www.cnblogs.com/SnowPhoenix/p/15699497.html