【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(),通过一个intType构建了一个*intType,进而创建了一个*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,还需要进行进一步初始化。

小结

typintuint之类的字面类型,记Typtyp对应的反射类型,即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

posted @ 2021-12-16 18:30  SnowPhoenix  阅读(2899)  评论(0编辑  收藏  举报