Go語(yǔ)言 反射 - reflect標(biāo)準(zhǔn)庫(kù)包中提供的反射支持

2023-02-16 17:38 更新

Go是一門(mén)具有良好反射支持的靜態(tài)語(yǔ)言。 本文將解釋?reflect?標(biāo)準(zhǔn)庫(kù)包中提供的反射功能。

在閱讀本剩下的部分之前,最好先閱讀Go類(lèi)型系統(tǒng)概述接口兩篇文章。

反射概述

Go中提供的反射功能帶來(lái)了很多動(dòng)態(tài)特性。 很多標(biāo)準(zhǔn)庫(kù),比如fmt和很多encoding包,均十分依賴(lài)于反射機(jī)制。

我們可以通過(guò)reflect庫(kù)包中TypeValue兩個(gè)類(lèi)型提供的功能來(lái)觀察不同的Go值。 本文下面的內(nèi)容將介紹如何使用這兩個(gè)類(lèi)型。

Go反射機(jī)制設(shè)計(jì)的目標(biāo)之一是任何非反射操作都可以通過(guò)反射機(jī)制來(lái)完成。 由于各種各樣的原因,此目標(biāo)并沒(méi)有得到100%的實(shí)現(xiàn)。 但是,目前大部分的非反射操作都可以通過(guò)反射機(jī)制來(lái)完成。 另一方面,通過(guò)反射,我們也可以完成一些使用非反射操作不可能完成的操作。 不能或者只能通過(guò)反射完成的操作將在下面的講解中提及。

reflect.Type類(lèi)型和值

通過(guò)調(diào)用reflect.TypeOf函數(shù),我們可以從一個(gè)任何非接口類(lèi)型的值創(chuàng)建一個(gè)reflect.Type值。 此reflect.Type值表示著此非接口值的類(lèi)型。通過(guò)此值,我們可以得到很多此非接口類(lèi)型的信息。 當(dāng)然,我們也可以將一個(gè)接口值傳遞給一個(gè)reflect.TypeOf函數(shù)調(diào)用,但是此調(diào)用將返回一個(gè)表示著此接口值的動(dòng)態(tài)類(lèi)型的reflect.Type值。 實(shí)際上,reflect.TypeOf函數(shù)的唯一參數(shù)的類(lèi)型為interface{}, reflect.TypeOf函數(shù)將總是返回一個(gè)表示著此唯一接口參數(shù)值的動(dòng)態(tài)類(lèi)型的reflect.Type值。 那如何得到一個(gè)表示著某個(gè)接口類(lèi)型的reflect.Type值呢? 我們必須通過(guò)下面將要介紹的一些間接途徑來(lái)達(dá)到這一目的。

類(lèi)型reflect.Type為一個(gè)接口類(lèi)型,它指定了若干方法。 通過(guò)這些方法,我們能夠觀察到一個(gè)reflect.Type值所表示的Go類(lèi)型的各種信息。 這些方法中的有些適用于所有種類(lèi)的類(lèi)型,有些只適用于一種或幾種類(lèi)型。 通過(guò)不合適的reflect.Type屬主值調(diào)用某個(gè)方法將在運(yùn)行時(shí)產(chǎn)生一個(gè)恐慌。 請(qǐng)閱讀reflect代碼庫(kù)中各個(gè)方法的文檔來(lái)獲取如何正確地使用這些方法。

一個(gè)例子:

package main

import "fmt"
import "reflect"

func main() {
	type A = [16]int16
	var c <-chan map[A][]byte
	tc := reflect.TypeOf(c)
	fmt.Println(tc.Kind())    // chan
	fmt.Println(tc.ChanDir()) // <-chan
	tm := tc.Elem()
	ta, tb := tm.Key(), tm.Elem()
	fmt.Println(tm.Kind(), ta.Kind(), tb.Kind()) // map array slice
	tx, ty := ta.Elem(), tb.Elem()

	// byte是uint8類(lèi)型的別名。
	fmt.Println(tx.Kind(), ty.Kind()) // int16 uint8
	fmt.Println(tx.Bits(), ty.Bits()) // 16 8
	fmt.Println(tx.ConvertibleTo(ty)) // true
	fmt.Println(tb.ConvertibleTo(ta)) // false

	// 切片類(lèi)型和映射類(lèi)型都是不可比較類(lèi)型。
	fmt.Println(tb.Comparable()) // false
	fmt.Println(tm.Comparable()) // false
	fmt.Println(ta.Comparable()) // true
	fmt.Println(tc.Comparable()) // true
}

目前,Go支持26種種類(lèi)的類(lèi)型。

在上面這個(gè)例子中,我們使用方法Elem來(lái)得到某些類(lèi)型的元素類(lèi)型。 實(shí)際上,此方法也可以用來(lái)得到一個(gè)指針類(lèi)型的基類(lèi)型。一個(gè)例子:

package main

import "fmt"
import "reflect"

type T []interface{m()}
func (T) m() {}

func main() {
	tp := reflect.TypeOf(new(interface{}))
	tt := reflect.TypeOf(T{})
	fmt.Println(tp.Kind(), tt.Kind()) // ptr slice

	// 使用間接的方法得到表示兩個(gè)接口類(lèi)型的reflect.Type值。
	ti, tim := tp.Elem(), tt.Elem()
	fmt.Println(ti.Kind(), tim.Kind()) // interface interface

	fmt.Println(tt.Implements(tim))  // true
	fmt.Println(tp.Implements(tim))  // false
	fmt.Println(tim.Implements(tim)) // true

	// 所有的類(lèi)型都實(shí)現(xiàn)了任何空接口類(lèi)型。
	fmt.Println(tp.Implements(ti))  // true
	fmt.Println(tt.Implements(ti))  // true
	fmt.Println(tim.Implements(ti)) // true
	fmt.Println(ti.Implements(ti))  // true
}

上面這個(gè)例子同時(shí)也展示了如何通過(guò)間接的途徑得到一個(gè)表示一個(gè)接口類(lèi)型的reflect.Type值。

我們可以通過(guò)反射列出一個(gè)類(lèi)型的所有方法和一個(gè)結(jié)構(gòu)體類(lèi)型的所有(導(dǎo)出和非導(dǎo)出)字段的類(lèi)型。 我們也可以通過(guò)反射列出一個(gè)函數(shù)類(lèi)型的各個(gè)輸入?yún)?shù)和返回結(jié)果類(lèi)型。

package main

import "fmt"
import "reflect"

type F func(string, int) bool
func (f F) m(s string) bool {
	return f(s, 32)
}
func (f F) M() {}

type I interface{m(s string) bool; M()}

func main() {
	var x struct {
		F F
		i I
	}
	tx := reflect.TypeOf(x)
	fmt.Println(tx.Kind())        // struct
	fmt.Println(tx.NumField())    // 2
	fmt.Println(tx.Field(1).Name) // i
	// 包路徑(PkgPath)是非導(dǎo)出字段(或者方法)的內(nèi)在屬性。
	fmt.Println(tx.Field(0).PkgPath) // 
	fmt.Println(tx.Field(1).PkgPath) // main

	tf, ti := tx.Field(0).Type, tx.Field(1).Type
	fmt.Println(tf.Kind())               // func
	fmt.Println(tf.IsVariadic())         // false
	fmt.Println(tf.NumIn(), tf.NumOut()) // 2 1
	t0, t1, t2 := tf.In(0), tf.In(1), tf.Out(0)
	// 下一行打印出:string int bool
	fmt.Println(t0.Kind(), t1.Kind(), t2.Kind())

	fmt.Println(tf.NumMethod(), ti.NumMethod()) // 1 2
	fmt.Println(tf.Method(0).Name)              // M
	fmt.Println(ti.Method(1).Name)              // m
	_, ok1 := tf.MethodByName("m")
	_, ok2 := ti.MethodByName("m")
	fmt.Println(ok1, ok2) // false true
}

從上面這個(gè)例子我們可以看出:

  1. 對(duì)于非接口類(lèi)型,reflect.Type.NumMethod方法只返回一個(gè)類(lèi)型的所有導(dǎo)出的方法(包括通過(guò)內(nèi)嵌得來(lái)的隱式方法)的個(gè)數(shù),并且 方法reflect.Type.MethodByName不能用來(lái)獲取一個(gè)類(lèi)型的非導(dǎo)出方法; 而對(duì)于接口類(lèi)型,則并無(wú)這些限制(Go 1.16之前的文檔對(duì)這兩個(gè)方法的描述不準(zhǔn)確,并沒(méi)有體現(xiàn)出這個(gè)差異)。 此情形同樣存在于下一節(jié)將要介紹的reflect.Value類(lèi)型上的相應(yīng)方法。
  2. 雖然reflect.Type.NumField方法返回一個(gè)結(jié)構(gòu)體類(lèi)型的所有字段(包括非導(dǎo)出字段)的數(shù)目,但是不推薦使用方法reflect.Type.FieldByName來(lái)獲取非導(dǎo)出字段。

我們可以通過(guò)反射來(lái)檢視結(jié)構(gòu)體字段的標(biāo)簽信息。 結(jié)構(gòu)體字段標(biāo)簽的類(lèi)型為reflect.StructTag,它的方法GetLookup用來(lái)檢視字段標(biāo)簽中的鍵值對(duì)。 一個(gè)例子:

package main

import "fmt"
import "reflect"

type T struct {
	X    int  `max:"99" min:"0" default:"0"`
	Y, Z bool `optional:"yes"`
}

func main() {
	t := reflect.TypeOf(T{})
	x := t.Field(0).Tag
	y := t.Field(1).Tag
	z := t.Field(2).Tag
	fmt.Println(reflect.TypeOf(x)) // reflect.StructTag
	// v的類(lèi)型為string
	v, present := x.Lookup("max")     
	fmt.Println(len(v), present)      // 2 true
	fmt.Println(x.Get("max"))         // 99
	fmt.Println(x.Lookup("optional")) //  false
	fmt.Println(y.Lookup("optional")) // yes true
	fmt.Println(z.Lookup("optional")) // yes true
}

注意:

  • 鍵值對(duì)中的鍵不能包含空格(Unicode值為32)、雙引號(hào)(Unicode值為34)和冒號(hào)(Unicode值為58)。
  • 為了形成鍵值對(duì),所設(shè)想的鍵值對(duì)形式中的冒號(hào)的后面不能緊跟著空格字符。所以
    `optional: "yes"`不形成鍵值對(duì)。
  • 鍵值對(duì)中的值中的空格不會(huì)被忽略。所以
    `json:"author, omitempty“`、
    `json:" author,omitempty“`以及
    `json:"author,omitempty“`各不相同。
  • 每個(gè)字段標(biāo)簽應(yīng)該呈現(xiàn)為單行才能使它的整個(gè)部分都對(duì)鍵值對(duì)的形成有貢獻(xiàn)。

reflect代碼包也提供了一些其它函數(shù)來(lái)動(dòng)態(tài)地創(chuàng)建出來(lái)一些無(wú)名組合類(lèi)型。

package main

import "fmt"
import "reflect"

func main() {
	ta := reflect.ArrayOf(5, reflect.TypeOf(123))
	fmt.Println(ta) // [5]int
	tc := reflect.ChanOf(reflect.SendDir, ta)
	fmt.Println(tc) // chan<- [5]int
	tp := reflect.PtrTo(ta)
	fmt.Println(tp) // *[5]int
	ts := reflect.SliceOf(tp)
	fmt.Println(ts) // []*[5]int
	tm := reflect.MapOf(ta, tc)
	fmt.Println(tm) // map[[5]int]chan<- [5]int
	tf := reflect.FuncOf([]reflect.Type{ta},
				[]reflect.Type{tp, tc}, false)
	fmt.Println(tf) // func([5]int) (*[5]int, chan<- [5]int)
	tt := reflect.StructOf([]reflect.StructField{
		{Name: "Age", Type: reflect.TypeOf("abc")},
	})
	fmt.Println(tt)            // struct { Age string }
	fmt.Println(tt.NumField()) // 1
}

上面的例子并未展示和reflect.Type相關(guān)的所有函數(shù)和方法。 請(qǐng)閱讀reflect標(biāo)準(zhǔn)庫(kù)代碼包的文檔以獲取如何使用這些函數(shù)和方法。

注意,到目前為止(Go 1.19),我們無(wú)法通過(guò)反射動(dòng)態(tài)創(chuàng)建一個(gè)接口類(lèi)型。這是Go反射目前的一個(gè)限制。

另一個(gè)限制是使用反射動(dòng)態(tài)創(chuàng)建結(jié)構(gòu)體類(lèi)型的時(shí)候可能會(huì)有各種不完美的情況出現(xiàn)。

第三個(gè)限制是我們無(wú)法通過(guò)反射來(lái)聲明一個(gè)新的類(lèi)型。

reflect.Value類(lèi)型和值

類(lèi)似的,我們可以通過(guò)調(diào)用reflect.ValueOf函數(shù),從一個(gè)非接口類(lèi)型的值創(chuàng)建一個(gè)reflect.Value值。 此reflect.Value值代表著此非接口值。 和reflect.TypeOf函數(shù)類(lèi)似,reflect.ValueOf函數(shù)也只有一個(gè)interface{}類(lèi)型的參數(shù)。 當(dāng)我們將一個(gè)接口值傳遞給一個(gè)reflect.ValueOf函數(shù)調(diào)用時(shí),此調(diào)用返回的是代表著此接口值的動(dòng)態(tài)值的一個(gè)reflect.Value值。 我們必須通過(guò)間接的途徑獲得一個(gè)代表一個(gè)接口值的reflect.Value值。

被一個(gè)reflect.Value值代表著的值常稱(chēng)為此reflect.Value值的底層值(underlying value)。

reflect.Value類(lèi)型有很多方法。 我們可以調(diào)用這些方法來(lái)觀察和操縱一個(gè)reflect.Value屬主值表示的Go值。 這些方法中的有些適用于所有種類(lèi)類(lèi)型的值,有些只適用于一種或幾種類(lèi)型的值。 通過(guò)不合適的reflect.Value屬主值調(diào)用某個(gè)方法將在運(yùn)行時(shí)產(chǎn)生一個(gè)恐慌。 請(qǐng)閱讀reflect代碼庫(kù)中各個(gè)方法的文檔來(lái)獲取如何正確地使用這些方法。

一個(gè)reflect.Value值的CanSet方法將返回此reflect.Value值代表的Go值是否可以被修改(可以被賦值)。 如果一個(gè)Go值可以被修改,則我們可以調(diào)用對(duì)應(yīng)的reflect.Value值的Set方法來(lái)修改此Go值。 注意:reflect.ValueOf函數(shù)直接返回的reflect.Value值都是不可修改的。

一個(gè)例子:

package main

import "fmt"
import "reflect"

func main() {
	n := 123
	p := &n
	vp := reflect.ValueOf(p)
	fmt.Println(vp.CanSet(), vp.CanAddr()) // false false
	vn := vp.Elem() // 取得vp的底層指針值引用的值的代表值
	fmt.Println(vn.CanSet(), vn.CanAddr()) // true true
	vn.Set(reflect.ValueOf(789)) // <=> vn.SetInt(789)
	fmt.Println(n)               // 789
}

一個(gè)結(jié)構(gòu)體值的非導(dǎo)出字段不能通過(guò)反射來(lái)修改。

package main

import "fmt"
import "reflect"

func main() {
	var s struct {
		X interface{} // 一個(gè)導(dǎo)出字段
		y interface{} // 一個(gè)非導(dǎo)出字段
	}
	vp := reflect.ValueOf(&s)
	// 如果vp代表著一個(gè)指針,下一行等價(jià)于"vs := vp.Elem()"。
	vs := reflect.Indirect(vp)
	// vx和vy都各自代表著一個(gè)接口值。
	vx, vy := vs.Field(0), vs.Field(1)
	fmt.Println(vx.CanSet(), vx.CanAddr()) // true true
	// vy is addressable but not modifiable.
	fmt.Println(vy.CanSet(), vy.CanAddr()) // false true
	vb := reflect.ValueOf(123)
	vx.Set(vb)     // okay, 因?yàn)関x代表的值是可修改的。
	// vy.Set(vb)  // 會(huì)造成恐慌,因?yàn)関y代表的值是不可修改的。
	fmt.Println(s) // {123 
	fmt.Println(vx.IsNil(), vy.IsNil()) // false true
}

上例中同時(shí)也展示了如何間接地獲取底層值為接口值的reflect.Value值。

從上兩例中,我們可以得知有兩種方法獲取一個(gè)代表著一個(gè)指針?biāo)弥闹档?code>reflect.Value值:

  1. 通過(guò)調(diào)用代表著此指針值的reflect.Value值的Elem方法。
  2. 將代表著此指針值的reflect.Value值的傳遞給一個(gè)reflect.Indirect函數(shù)調(diào)用。 (如果傳遞給一個(gè)reflect.Indirect函數(shù)調(diào)用的實(shí)參不代表著一個(gè)指針值,則此調(diào)用返回此實(shí)參的一個(gè)復(fù)制。)

注意:reflect.Value.Elem方法也可以用來(lái)獲取一個(gè)代表著一個(gè)接口值的動(dòng)態(tài)值的reflect.Value值,比如下例中所示。

package main

import "fmt"
import "reflect"

func main() {
	var z = 123
	var y = &z
	var x interface{} = y
	v := reflect.ValueOf(&x)
	vx := v.Elem()
	vy := vx.Elem()
	vz := vy.Elem()
	vz.Set(reflect.ValueOf(789))
	fmt.Println(z) // 789
}

reflect標(biāo)準(zhǔn)庫(kù)包中也提供了一些對(duì)應(yīng)著內(nèi)置函數(shù)或者各種非反射功能的函數(shù)。 下面這個(gè)例子展示了如何利用這些函數(shù)將一個(gè)(效率不高的)自定義泛型函數(shù)綁定到不同的類(lèi)型的函數(shù)值上。

package main

import "fmt"
import "reflect"

func InvertSlice(args []reflect.Value) []reflect.Value {
	inSlice, n := args[0], args[0].Len()
	outSlice := reflect.MakeSlice(inSlice.Type(), 0, n)
	for i := n-1; i >= 0; i-- {
		element := inSlice.Index(i)
		outSlice = reflect.Append(outSlice, element)
	}
	return []reflect.Value{outSlice}
}

func Bind(p interface{}, 
		f func ([]reflect.Value) []reflect.Value) {
	// invert代表著一個(gè)函數(shù)值。
	invert := reflect.ValueOf(p).Elem()
	invert.Set(reflect.MakeFunc(invert.Type(), f))
}

func main() {
	var invertInts func([]int) []int
	Bind(&invertInts, InvertSlice)
	fmt.Println(invertInts([]int{2, 3, 5})) // [5 3 2]

	var invertStrs func([]string) []string
	Bind(&invertStrs, InvertSlice)
	fmt.Println(invertStrs([]string{"Go", "C"})) // [C Go]
}

如果一個(gè)reflect.Value值的底層值為一個(gè)函數(shù)值,則我們可以調(diào)用此reflect.Value值的Call方法來(lái)調(diào)用此函數(shù)。 每個(gè)Call方法調(diào)用接受一個(gè)[]reflect.Value類(lèi)型的參數(shù)(表示傳遞給相應(yīng)函數(shù)調(diào)用的各個(gè)實(shí)參)并返回一個(gè)同類(lèi)型結(jié)果(表示相應(yīng)函數(shù)調(diào)用返回的各個(gè)結(jié)果)。

package main

import "fmt"
import "reflect"

type T struct {
	A, b int
}

func (t T) AddSubThenScale(n int) (int, int) {
	return n * (t.A + t.b), n * (t.A - t.b)
}

func main() {
	t := T{5, 2}
	vt := reflect.ValueOf(t)
	vm := vt.MethodByName("AddSubThenScale")
	results := vm.Call([]reflect.Value{reflect.ValueOf(3)})
	fmt.Println(results[0].Int(), results[1].Int()) // 21 9

	neg := func(x int) int {
		return -x
	}
	vf := reflect.ValueOf(neg)
	fmt.Println(vf.Call(results[:1])[0].Int()) // -21
	fmt.Println(vf.Call([]reflect.Value{
		vt.FieldByName("A"), // 如果是字段b,則造成恐慌
	})[0].Int()) // -5
}

請(qǐng)注意:非導(dǎo)出結(jié)構(gòu)體字段值不能用做反射函數(shù)調(diào)用中的實(shí)參。 如果上例中的vt.FieldByName("A")被替換為vt.FieldByName("b"),則將產(chǎn)生一個(gè)恐慌。

下面是一個(gè)使用映射反射值的例子。

package main

import "fmt"
import "reflect"

func main() {
	valueOf := reflect.ValueOf
	m := map[string]int{"Unix": 1973, "Windows": 1985}
	v := valueOf(m)
	// 第二個(gè)實(shí)參為Value零值時(shí),表示刪除一個(gè)映射條目。
	v.SetMapIndex(valueOf("Windows"), reflect.Value{})
	v.SetMapIndex(valueOf("Linux"), valueOf(1991))
	for i := v.MapRange(); i.Next(); {
		fmt.Println(i.Key(), "\t:", i.Value())
	}
}

注意:方法reflect.Value.MapRange方法是從Go 1.12開(kāi)始才支持的。

下面是一個(gè)使用通道反射值的例子。

package main

import "fmt"
import "reflect"

func main() {
	c := make(chan string, 2)
	vc := reflect.ValueOf(c)
	vc.Send(reflect.ValueOf("C"))
	succeeded := vc.TrySend(reflect.ValueOf("Go"))
	fmt.Println(succeeded) // true
	succeeded = vc.TrySend(reflect.ValueOf("C++"))
	fmt.Println(succeeded) // false
	fmt.Println(vc.Len(), vc.Cap()) // 2 2
	vs, succeeded := vc.TryRecv()
	fmt.Println(vs.String(), succeeded) // C true
	vs, sentBeforeClosed := vc.Recv()
	fmt.Println(vs.String(), sentBeforeClosed) // Go true
	vs, succeeded = vc.TryRecv()
	fmt.Println(vs.String()) // 
	fmt.Println(succeeded)   // false
}

reflect.Value類(lèi)型的TrySendTryRecv方法對(duì)應(yīng)著只有一個(gè)case分支和一個(gè)default分支的select流程控制代碼塊

我們可以使用reflect.Select函數(shù)在運(yùn)行時(shí)刻來(lái)模擬具有不定case分支數(shù)量的select流程控制代碼塊。

package main

import "fmt"
import "reflect"

func main() {
	c := make(chan int, 1)
	vc := reflect.ValueOf(c)
	succeeded := vc.TrySend(reflect.ValueOf(123))
	fmt.Println(succeeded, vc.Len(), vc.Cap()) // true 1 1

	vSend, vZero := reflect.ValueOf(789), reflect.Value{}
	branches := []reflect.SelectCase{
		{Dir: reflect.SelectDefault, Chan: vZero, Send: vZero},
		{Dir: reflect.SelectRecv, Chan: vc, Send: vZero},
		{Dir: reflect.SelectSend, Chan: vc, Send: vSend},
	}
	selIndex, vRecv, sentBeforeClosed := reflect.Select(branches)
	fmt.Println(selIndex)         // 1
	fmt.Println(sentBeforeClosed) // true
	fmt.Println(vRecv.Int())      // 123
	vc.Close()
	// 再模擬一次select流程控制代碼塊。因?yàn)関c已經(jīng)關(guān)閉了,
	// 所以需將最后一個(gè)case分支去除,否則它可能會(huì)造成一個(gè)恐慌。
	selIndex, _, sentBeforeClosed = reflect.Select(branches[:2])
	fmt.Println(selIndex, sentBeforeClosed) // 1 false
}

一些reflect.Value值可能表示著不合法的Go值。 這樣的值為reflect.Value類(lèi)型的零值(即沒(méi)有底層值的reflect.Value值)。

package main

import "reflect"
import "fmt"

func main() {
	var z reflect.Value // 一個(gè)reflect.Value零值
	fmt.Println(z)      // 
	v := reflect.ValueOf((*int)(nil)).Elem()
	fmt.Println(v)      // 
	fmt.Println(v == z) // true
	var i = reflect.ValueOf([]interface{}{nil}).Index(0)
	fmt.Println(i)             // 
	fmt.Println(i.Elem())      // 
	fmt.Println(i.Elem() == z) // true
}

從上面的例子中,我們知道,使用空接口interface{}值做為中介,一個(gè)Go值可以轉(zhuǎn)換為一個(gè)reflect.Value值。 逆過(guò)程類(lèi)似,通過(guò)調(diào)用一個(gè)reflect.Value值的Interface方法得到一個(gè)interface{}值,然后將此interface{}斷言為原來(lái)的Go值。 但是,請(qǐng)注意,調(diào)用一個(gè)代表著非導(dǎo)出字段的reflect.Value值的Interface方法將導(dǎo)致一個(gè)恐慌。

package main

import (
	"fmt"
	"reflect"
	"time"
)

func main() {
	vx := reflect.ValueOf(123)
	vy := reflect.ValueOf("abc")
	vz := reflect.ValueOf([]bool{false, true})
	vt := reflect.ValueOf(time.Time{})

	x := vx.Interface().(int)
	y := vy.Interface().(string)
	z := vz.Interface().([]bool)
	m := vt.MethodByName("IsZero").Interface().(func() bool)
	fmt.Println(x, y, z, m()) // 123 abc [false true] true

	type T struct {x int}
	t := &T{3}
	v := reflect.ValueOf(t).Elem().Field(0)
	fmt.Println(v)             // 3
	fmt.Println(v.Interface()) // panic
}

Value.IsZero方法是Go 1.13中引進(jìn)的,此方法用來(lái)查看一個(gè)值是否為零值。

從Go 1.17開(kāi)始,一個(gè)切片可以被轉(zhuǎn)化為一個(gè)相同元素類(lèi)型的數(shù)組的指針類(lèi)型。 但是如果在這樣的一個(gè)轉(zhuǎn)換中數(shù)組類(lèi)型的長(zhǎng)度過(guò)長(zhǎng),將導(dǎo)致恐慌產(chǎn)生。 因此Go 1.17同時(shí)引入了一個(gè)Value.CanConvert(T Type)方法,用來(lái)檢查一個(gè)轉(zhuǎn)換是否會(huì)成功(即不會(huì)產(chǎn)生恐慌)。

一個(gè)使用了CanConvert方法的例子:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	s := reflect.ValueOf([]int{1, 2, 3, 4, 5})
	ts := s.Type()
	t1 := reflect.TypeOf(&[5]int{})
	t2 := reflect.TypeOf(&[6]int{})
	fmt.Println(ts.ConvertibleTo(t1)) // true
	fmt.Println(ts.ConvertibleTo(t2)) // true
	fmt.Println(s.CanConvert(t1))     // true
	fmt.Println(s.CanConvert(t2))     // false
}

上面這些例子并未觸及到所有的和reflect.Value相關(guān)的函數(shù)和方法,請(qǐng)閱讀reflect標(biāo)準(zhǔn)庫(kù)包的文檔以獲取如何使用這些函數(shù)和方法。 另外請(qǐng)注意Go細(xì)節(jié)101中提到的一些關(guān)于反射的細(xì)節(jié)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)