【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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM