zoukankan      html  css  js  c++  java
  • 【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

  • 相关阅读:
    SpringMVC中静态获取request对象 Spring中获取 HttpServletRequest对象【转载】
    springcloud 的loadbalancer 轮询算法切换方法 2021.4.3
    springboot项目启动增加图标
    rabbitmq 端口作用以及修改方法
    centos8 安装rabbitmq
    springcloud config client Value获取不到信息的问题的处理方法
    springcloud config配置git作为数据源然后启动报错 If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    Sublime Text的列模式如何操作
    centos8 安装redis
    jQuery简单的Ajax调用
  • 原文地址:https://www.cnblogs.com/SnowPhoenix/p/15699497.html
Copyright © 2011-2022 走看看