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

  • 相关阅读:
    搭建个人Spring-Initializr服务器
    “不蒜子”统计总访问人数脚本
    基于Hazelcast及Kafka实现的分布式锁与集群负载均衡
    虚拟机部署hadoop集群
    程序员、黑客及开发者之间的区别
    今日校园自动登录教程
    逆向DES算法
    来自穷逼对HttpCanary的蹂躏
    今日校园提交签到和查寝-Java实现
    JS 判断数据类型方法
  • 原文地址:https://www.cnblogs.com/SnowPhoenix/p/15699497.html
Copyright © 2011-2022 走看看