阮小坏 發表於 2021-12-16 18:30:00

【Go反射】创建对象

<h1 id="前言">前言</h1>
<p>最近在写一个自动配置的库cfgm,其中序列化和反序列化的过程用到了大量反射,主要部分写完之后,我在这里回顾总结一下反射的基本操作。</p>
<p>第一篇【Go反射】读取对象中总结了利用反射读取对象的方法。</p>
<p>第二篇【Go反射】修改对象中总结了利用反射修改对象的方法。</p>
<p>本篇总结一下创建操作,即创建新的简单类型(int、uint、float、bool、string)、指针、切片、数组、map、结构体对象。</p>
<p>先声明一下后续代码中需要引入的包:</p>
<pre><code class="language-Go">import (
        "github.com/stretchr/testify/assert"
        "reflect"
        "testing"
)
</code></pre>
<h1 id="参考">参考</h1>
<ul>
<li>go.dev | reflect</li>
</ul>
<h1 id="目录">目录</h1>
<ul>
<li>前言</li>
<li>参考</li>
<li>目录</li>
<li>reflect.New()</li>
<li>通过基础类型构造复杂类型
<ul>
<li>创建指针Type</li>
<li>创建数组Type</li>
<li>创建切片Type</li>
<li>创建mapType</li>
<li>小结</li>
</ul>
</li>
<li>reflect.MakeXXX()
<ul>
<li>make创建切片</li>
<li>make创建map</li>
</ul>
</li>
<li>其它创建对象的方法
<ul>
<li>reflect.Zero()</li>
<li>reflect.Append()</li>
<li>reflect.AppendSlice()</li>
<li>.Slice() 和 .Slice3()</li>
</ul>
</li>
<li>总结</li>
</ul>
<h1 id="reflectnew">reflect.New()</h1>
<p>利用反射创建对象其实十分简单,<code>reflect.New()</code>提供了类似内置函数<code>new()</code>的功能:</p>
<ul>
<li><code>new()</code>返回指定类型的指针,该指针指向新创建的对象;</li>
<li><code>reflect.New()</code>返回指定类型指针的反射对象(Value结构体),该结构体解引用即可过的新创建的对象的反射对象;</li>
</ul>
<p>举个例子:</p>
<pre><code class="language-Go">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))
}
</code></pre>
<p>它其实相当于:</p>
<pre><code class="language-Go">func TestCreateSimple_WithoutReflect(t *testing.T) {
        ptr := new(int)
        *ptr = 123
        assert.Equal(t, 123, *ptr)
}
</code></pre>
<p>只不过后者需要一个硬编码的<code>int</code>传入<code>new()</code>,而利用反射可以通过一个<code>Type</code>对象(其实是个接口)来创建任何给定的对象。</p>
<p><code>reflect.New()</code>选择返回一个指针的反射对象,而不是直接返回目标对象的反射对象,一方面是为了和内置函数<code>new()</code>的行为相统一,另一方面通过返回指针然后进行解引用的操作,使得刚刚被创建的对象是<code>addressable</code>的,也就意味着它是可修改的(有关这一部分,请参考上一篇)。</p>
<p>我们可以轻松地通过<code>reflect.New</code>反射创建任何类型的对象,但是其前提是获得需要创建的对象的<code>Type</code>,这也是主要的难点所在。</p>
<h1 id="通过基础类型构造复杂类型">通过基础类型构造复杂类型</h1>
<p>Go的<code>reflect</code>库提供了很多方法,能够通过基础类型的<code>Type</code>对象,创建出复杂类型的<code>Type</code>对象,从而创建出复杂类型的反射对象(Value结构体)。</p>
<h2 id="创建指针type">创建指针Type</h2>
<pre><code class="language-Go">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))
}
</code></pre>
<p>利用<code>reflect.PtrTo()</code>,通过一个<code>int</code>的<code>Type</code>构建了一个<code>*int</code>的<code>Type</code>,进而创建了一个<code>*int</code>指针对象,注意创建的指针为nil指针。</p>
<h2 id="创建数组type">创建数组Type</h2>
<pre><code class="language-Go">func TestCreateArray_FromBase(t *testing.T) {
        typ := reflect.TypeOf(int(0))

        arrType := reflect.ArrayOf(4, typ)
        arrValue := reflect.New(arrType).Elem()
        assert.Equal(t, int{}, arrValue.Interface().(int))
}
</code></pre>
<p>长度是一个数组的类型的一部分,通过基类型构造数组类型时,需要指定数组长度。</p>
<p>创建的数组中的每一个元素都是默认初始化的。</p>
<h2 id="创建切片type">创建切片Type</h2>
<pre><code class="language-Go">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))
}
</code></pre>
<p>注意创建的切片是一个nil切片,而不是一个长度为0的切片。还需要通过<code>.Set()</code>和<code>reflect.MakeSlice()</code>的配合来进行进一步初始化(后文再讨论)。</p>
<h2 id="创建maptype">创建mapType</h2>
<pre><code class="language-Go">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(mapstring), mapValue.Interface().(mapstring))
}
</code></pre>
<p><code>reflect.MapOf()</code>接受两个<code>Type</code>,分别是map的key和value的<code>Type</code>。</p>
<p>和切片一样,创建的map是一个nil的map,而不是容量为0的map,还需要进行进一步初始化。</p>
<h2 id="小结">小结</h2>
<p>记<code>typ</code>为<code>int</code>、<code>uint</code>之类的字面类型,记<code>Typ</code>为<code>typ</code>对应的反射类型,即<code>Typ := reflect.TypeOf(typ(0))</code></p>
<table>
<thead>
<tr>
<th style="text-align: left">目标Type</th>
<th style="text-align: left">函数</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">*typ</td>
<td style="text-align: left">reflect.PtrTo(Typ)</td>
</tr>
<tr>
<td style="text-align: left">[...]typ</td>
<td style="text-align: left">reflect.ArrayOf(Typ)</td>
</tr>
<tr>
<td style="text-align: left">[]typ</td>
<td style="text-align: left">reflect.SliceOf(Typ)</td>
</tr>
<tr>
<td style="text-align: left">maptyp2</td>
<td style="text-align: left">reflect.MapOf(Typ1, Typ2)</td>
</tr>
<tr>
<td style="text-align: left">chan typ</td>
<td style="text-align: left">reflect.ChanOf(Typ)</td>
</tr>
</tbody>
</table>
<h1 id="reflectmakexxx">reflect.MakeXXX()</h1>
<p><code>reflect</code>包提供了一系列<code>MakeXXX()</code>的方法,对应内置函数<code>make()</code>。</p>
<h2 id="make创建切片">make创建切片</h2>
<pre><code class="language-Go">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)))
}
</code></pre>
<p><code>reflect.MakeSlice()</code>类似于<code>make()</code>,是实际创建切片的函数。</p>
<p>不过需要注意的是,<code>reflect.MakeSlice()</code>的返回值并不是一个<code>addressable</code>的切片反射对象。</p>
<p>虽然切片的元素是<code>addressable</code>的,我们仍旧可以直接更改切片内的元素,但是切片的长度、容量我们无法直接更改。</p>
<p>因此,如果我们在<code>MakeSlice()</code>之后还需要更改切片的长度和容量,就还是需要先通过<code>.Set()</code>将其赋值给一个<code>addressable</code>的对象再进行修改,但我实在想象不出什么情况下需要这么干,因为可以在<code>reflect.MakeSlice()</code>的时候进行指定。</p>
<h2 id="make创建map">make创建map</h2>
<pre><code class="language-Go">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, mapint{"A":1}, dictValue.Interface().(mapint))
}
</code></pre>
<p><code>reflect.MakeMap()</code>和<code>reflect.MakeSlice()</code>类似,创建的都是一个非<code>addressable</code>的反射对象。不过<code>.SetMapIndex()</code>并不要求map反射对象是<code>addressable</code>的,所以也无伤大雅。</p>
<h1 id="其它创建对象的方法">其它创建对象的方法</h1>
<h2 id="reflectzero">reflect.Zero()</h2>
<p>这个函数用于创建零值,和<code>reflect.New()</code>不同,后者虽然创建的新对象也是零值,但是通过解引用可以获得一个<code>addressable</code>的反射对象,而<code>reflect.Zero()</code>返回的则是一个<code>.CanAddr()</code>为<code>false</code>的反射对象。从源码上来看,所有通过<code>reflect.Zero()</code>创建的小对象都是公用同一块被置零的空间的。</p>
<p><code>reflect.Zero()</code>大部分时候是配合Value结构体的<code>.Set()</code>方法来使用的。下面通过将指针置为nil来进行示例:</p>
<pre><code class="language-Go">func TestCreatePtr_Nil(t *testing.T) {
        var integer int = 1
        ptr := &amp;integer
        ptrValue := reflect.ValueOf(&amp;ptr).Elem()

        ptrValue.Set(reflect.Zero(ptrValue.Type()))
        assert.Equal(t, (*int)(nil), ptr)
}
</code></pre>
<h2 id="reflectappend">reflect.Append()</h2>
<p>这个函数模拟了内置函数<code>append</code></p>
<pre><code class="language-Go">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))
}
</code></pre>
<p><code>reflect.Append()</code>传入一个切片的反射对象和一个追加的元素的反射对象,返回一个新的反射切片反射对象。传入的反射对象不要求是<code>addressable</code>的,返回的反射对象也不是<code>addressable</code>的。</p>
<p>不过有时我们需要模拟<code>slice = append(slice, elem)</code>,这个时候就需要切片的反射对象是<code>addressable</code>的(因为需要对它调用<code>.Set()</code>):</p>
<pre><code class="language-Go">func TestSetSlice_Append(t *testing.T) {
        slice := []int{1, 2, 3}
        sliceValue := reflect.ValueOf(&amp;slice).Elem()
        elemValue := reflect.ValueOf(int(4))

        sliceValue.Set(reflect.Append(sliceValue, elemValue))
        assert.Equal(t, []int{1, 2, 3, 4}, slice)
}
</code></pre>
<h2 id="reflectappendslice">reflect.AppendSlice()</h2>
<p>对应于<code>append(slice1, slice2...)</code>,<code>reflect</code>包提供了<code>reflect.AppendSlice()</code>:</p>
<pre><code class="language-Go">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))
}
</code></pre>
<p><code>slice1 = append(slice1, slice2...)</code>的效果同理<code>reflect.Append()</code>,这里不多赘述。</p>
<h2 id="slice-和-slice3">.Slice() 和 .Slice3()</h2>
<pre><code class="language-Go">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)))
}
</code></pre>
<p>对一个切片的反射对象或者对一个数组的反射对象调用<code>.Slice(i, j)</code>来模拟<code>slice := array</code>,调用<code>.Slice(i, j, k)</code>来模拟<code>slice := array</code>。</p>
<p>需要注意的是,如果是对切片的反射对象调用,并不要求该反射对象是<code>addressable</code>的,但是如果对数组调用,则需要时<code>addressable</code>的:</p>
<pre><code class="language-Go">func TestCreatSlice_Slice(t *testing.T) {
        array := [...]int{1, 2, 3, 4}
        arrayValue := reflect.ValueOf(&amp;array).Elem()

        newSlice := arrayValue.Slice(1, 3)
        assert.False(t, newSlice.CanAddr())
        assert.Equal(t, []int{2, 3}, newSlice.Interface().([]int))
}
</code></pre>
<p>和<code>reflect.MakeXXX()</code>以及<code>reflect.Append()</code>、<code>reflect.AppendSlice()</code>类似,通过<code>.Slice()</code>和<code>.Slice3()</code>创建的切片的Value结构体也不是<code>addressable</code>的。</p>
<h1 id="总结">总结</h1>
<p>本文介绍了通过<code>reflect.New()</code>和<code>reflect</code>包中各种构造复杂Type的方法来创建对象的方法,并介绍了一系列能够产生新的非<code>addressable</code>对象的方法。</p>
<p>转载请注明原文地址:https://www.cnblogs.com/SnowPhoenix/p/15699497.html</p><br><br>
来源:https://www.cnblogs.com/SnowPhoenix/p/15699497.html
頁: [1]
查看完整版本: 【Go反射】创建对象