日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

深入了解Golang中reflect反射基本原理_Golang

作者:rubys_ ? 更新時(shí)間: 2023-02-11 編程語(yǔ)言

反射概述

反射是這樣一種機(jī)制,它是可以讓我們?cè)诔绦蜻\(yùn)行時(shí)(runtime)訪問(wèn)、檢測(cè)和修改對(duì)象本身狀態(tài)或行為的一種能力。 比如,從一個(gè)變量推斷出其類型信息、以及存儲(chǔ)的數(shù)據(jù)的一些信息,又或者獲取一個(gè)對(duì)象有什么方法可以調(diào)用等。 反射經(jīng)常用在一些需要同時(shí)處理不同類型變量的地方,比如序列化、反序列化、ORM 等等,如標(biāo)準(zhǔn)庫(kù)里面的 json.Marshal。

反射基礎(chǔ) - go 的 interface 是怎么存儲(chǔ)的

在正式開(kāi)始講解反射之前,我們有必要了解一下 go 里的接口(interface)是怎么存儲(chǔ)的。 關(guān)于這個(gè)問(wèn)題,在我的另外一篇文章中已經(jīng)做了很詳細(xì)的講解 go interface 設(shè)計(jì)與實(shí)現(xiàn), 這里不再贅述。但還是簡(jiǎn)單說(shuō)一下,go 的接口是由兩部分組成的,一部分是類型信息,另一部分是數(shù)據(jù)信息,如:

var?a?=?1
var?b?interface{}?=?a

對(duì)于這個(gè)例子,b 的類型信息是 int,數(shù)據(jù)信息是 1,這兩部分信息都是存儲(chǔ)在 b 里面的。b 的內(nèi)存結(jié)構(gòu)如下:

在上圖中,b 的類型實(shí)際上是 eface,它是一個(gè)空接口,它的定義如下:

type?eface?struct?{
????_type?*_type
????data??unsafe.Pointer
}

也就是說(shuō),一個(gè) interface{} 中實(shí)際上既包含了變量的類型信息,也包含了類型的數(shù)據(jù)。正因?yàn)槿绱?,我們才可以通過(guò)反射來(lái)獲取到變量的類型信息,以及變量的數(shù)據(jù)信息。

反射對(duì)象 - reflect.Type 和 reflect.Value

知道了 interface{} 的內(nèi)存結(jié)構(gòu)之后,我們就可以開(kāi)始講解反射了。反射的核心是兩個(gè)對(duì)象,分別是 reflect.Typereflect.Value。 它們分別代表了 go 語(yǔ)言中的類型和值。我們可以通過(guò) reflect.TypeOfreflect.ValueOf 來(lái)獲取到一個(gè)變量的類型和值。

var?a?=?1
t?:=?reflect.TypeOf(a)

var?b?=?"hello"
t1?:=?reflect.ValueOf(b)

我們?nèi)タ匆幌?TypeOfValueOf 的源碼會(huì)發(fā)現(xiàn),這兩個(gè)方法都接收一個(gè) interface{} 類型的參數(shù),然后返回一個(gè) reflect.Typereflect.Value 類型的值。這也就是為什么我們可以通過(guò) reflect.TypeOfreflect.ValueOf 來(lái)獲取到一個(gè)變量的類型和值的原因。

反射定律

在 go 官方博客中關(guān)于反射的文章 laws-of-reflection 中,提到了三條反射定律:

  • 反射可以將 interface 類型變量轉(zhuǎn)換成反射對(duì)象。
  • 反射可以將反射對(duì)象還原成 interface 對(duì)象。
  • 如果要修改反射對(duì)象,那么反射對(duì)象必須是可設(shè)置的(CanSet)。

關(guān)于這三條定律,官方博客已經(jīng)有了比較完整的闡述,感興趣的可以去看一下官方博客的文章。這里簡(jiǎn)單闡述一下:

反射可以將 interface 類型變量轉(zhuǎn)換成反射對(duì)象。

其實(shí)也就是上面的 reflect.Typereflect.Value,我們可以通過(guò) reflect.TypeOfreflect.ValueOf 來(lái)獲取到一個(gè)變量的反射類型和反射值。

var?a?=?1
typeOfA?:=?reflect.TypeOf(a)
valueOfA?:=?reflect.ValueOf(a)

反射可以將反射對(duì)象還原成 interface 對(duì)象。

我們可以通過(guò) reflect.Value.Interface 來(lái)獲取到反射對(duì)象的 interface 對(duì)象,也就是傳遞給 reflect.ValueOf 的那個(gè)變量本身。 不過(guò)返回值類型是 interface{},所以我們需要進(jìn)行類型斷言。

i?:=?valueOfA.Interface()
fmt.Println(i.(int))

如果要修改反射對(duì)象,那么反射對(duì)象必須是可設(shè)置的(CanSet)。

我們可以通過(guò) reflect.Value.CanSet 來(lái)判斷一個(gè)反射對(duì)象是否是可設(shè)置的。如果是可設(shè)置的,我們就可以通過(guò) reflect.Value.Set 來(lái)修改反射對(duì)象的值。 這其實(shí)也是非常場(chǎng)景的使用反射的一個(gè)場(chǎng)景,通過(guò)反射來(lái)修改變量的值。

var?x?float64?=?3.4
v?:=?reflect.ValueOf(&x)
fmt.Println("settability?of?v:",?v.CanSet())?//?false
fmt.Println("settability?of?v:",?v.Elem().CanSet())?//?true

那什么情況下一個(gè)反射對(duì)象是可設(shè)置的呢?前提是這個(gè)反射對(duì)象是一個(gè)指針,然后這個(gè)指針指向的是一個(gè)可設(shè)置的變量。 在我們傳遞一個(gè)值給 reflect.ValueOf 的時(shí)候,如果這個(gè)值只是一個(gè)普通的變量,那么 reflect.ValueOf 會(huì)返回一個(gè)不可設(shè)置的反射對(duì)象。 因?yàn)檫@個(gè)值實(shí)際上被拷貝了一份,我們?nèi)绻ㄟ^(guò)反射修改這個(gè)值,那么實(shí)際上是修改的這個(gè)拷貝的值,而不是原來(lái)的值。 所以 go 語(yǔ)言在這里做了一個(gè)限制,如果我們傳遞進(jìn) reflect.ValueOf 的變量是一個(gè)普通的變量,那么在我們?cè)O(shè)置反射對(duì)象的值的時(shí)候,會(huì)報(bào)錯(cuò)。 所以在上面這個(gè)例子中,我們傳遞了 x 的指針變量作為參數(shù)。這樣,運(yùn)行時(shí)就可以找到 x 本身,而不是 x 的拷貝,所以就可以修改 x 的值了。

但同時(shí)我們也注意到了,在上面這個(gè)例子中,v.CanSet() 返回的是 false,而 v.Elem().CanSet() 返回的是 true。 這是因?yàn)椋?code>v 是一個(gè)指針,而 v.Elem() 是指針指向的值,對(duì)于這個(gè)指針本身,我們修改它是沒(méi)有意義的,我們可以設(shè)想一下, 如果我們修改了指針變量(也就是修改了指針變量指向的地址),那會(huì)發(fā)生什么呢?那樣我們的指針變量就不是指向 x 了, 而是指向了其他的變量,這樣就不符合我們的預(yù)期了。所以 v.CanSet() 返回的是 false。

v.Elem().CanSet() 返回的是 true。這是因?yàn)?v.Elem() 才是 x 本身,通過(guò) v.Elem() 修改 x 的值是沒(méi)有問(wèn)題的。

Elem 方法

不知道有多少讀者和我一樣,在初次使用 go 的反射的時(shí)候,被 Elem 這個(gè)方法搞得一頭霧水。Elem 方法的作用是什么呢?在回答這個(gè)問(wèn)題之前,我們需要明確一點(diǎn):reflect.Valuereflect.Type 這兩個(gè)反射對(duì)象都有 Elem 方法,既然是不同的對(duì)象,那么它們的作用自然是不一樣的。

reflect.Value 的 Elem 方法

reflect.ValueElem 方法的作用是獲取指針指向的值,或者獲取接口的動(dòng)態(tài)值。也就是說(shuō),能調(diào)用 Elem 方法的反射對(duì)象,必須是一個(gè)指針或者一個(gè)接口。 在使用其他類型的 reflect.Value 來(lái)調(diào)用 Elem 方法的時(shí)候,會(huì) panic:

var?a?=?1
//?panic:?reflect:?call?of?reflect.Value.Elem?on?int?Value
reflect.ValueOf(a).Elem()

//?不報(bào)錯(cuò)
var?b?=?&a
reflect.ValueOf(b).Elem()

對(duì)于指針很好理解,其實(shí)作用類似解引用。而對(duì)于接口,還是要回到 interface 的結(jié)構(gòu)本身,因?yàn)榻涌诶锇祟愋秃蛿?shù)據(jù)本身,所以 Elem 方法就是獲取接口的數(shù)據(jù)部分(也就是 ifaceeface 中的 data 字段)。

指針類型:

接口類型:

reflect.Type 的 Elem 方法

reflect.TypeElem 方法的作用是獲取數(shù)組、chan、map、指針、切片關(guān)聯(lián)元素的類型信息,也就是說(shuō),對(duì)于 reflect.Type 來(lái)說(shuō), 能調(diào)用 Elem 方法的反射對(duì)象,必須是數(shù)組、chan、map、指針、切片中的一種,其他類型的 reflect.Type 調(diào)用 Elem 方法會(huì) panic。

示例:

t1?:=?reflect.TypeOf([3]int{1,?2,?3})?//?數(shù)組?[3]int
fmt.Println(t1.String())?//?[3]int
fmt.Println(t1.Elem().String())?//?int

需要注意的是,如果我們要獲取 map 類型 key 的類型信息,需要使用 Key 方法,而不是 Elem 方法。

m?:=?make(map[string]string)
t1?:=?reflect.TypeOf(m)
fmt.Println(t1.Key().String())?//?string

Interface 方法

這也是非常常用的一個(gè)方法,reflect.ValueInterface 方法的作用是獲取反射對(duì)象的動(dòng)態(tài)值。 也就是說(shuō),如果反射對(duì)象是一個(gè)指針,那么 Interface 方法會(huì)返回指針指向的值。

簡(jiǎn)單來(lái)說(shuō),如果 var i interface{} = x,那么 reflect.ValueOf(x).Interface() 就是 i 本身,只不過(guò)其類型是 interface{} 類型。

Kind

說(shuō)到反射,不得不提的另外一個(gè)話題就是 go 的類型系統(tǒng),對(duì)于開(kāi)發(fā)者來(lái)說(shuō),我們可以基于基本類型來(lái)定義各種新的類型,如:

//?Kind?是?int
type?myIny?int
//?Kind?是?Struct
type?Person?struct?{
????Name?string
????Age?int
}

但是不管我們定義了多少種類型,在 go 看來(lái)都是下面的基本類型中的一個(gè):

type?Kind?uint

const?(
?Invalid?Kind?=?iota
?Bool
?Int
?Int8
?Int16
?Int32
?Int64
?Uint
?Uint8
?Uint16
?Uint32
?Uint64
?Uintptr
?Float32
?Float64
?Complex64
?Complex128
?Array
?Chan
?Func
?Interface
?Map
?Pointer
?Slice
?String
?Struct
?UnsafePointer
)

也就是說(shuō),我們定義的類型在 go 的類型系統(tǒng)中都是基本類型的一種,這個(gè)基本類型就是 Kind。 也正因?yàn)槿绱?,我們可以通過(guò)有限的 reflect.TypeKind 來(lái)進(jìn)行類型判斷。 也就是說(shuō),我們?cè)谕ㄟ^(guò)反射來(lái)判斷變量的類型的時(shí)候,只需要枚舉 Kind 中的類型,然后通過(guò) reflect.TypeKind 方法來(lái)判斷即可。

Type 表示的是反射對(duì)象(Type 對(duì)象是某一個(gè) Kind,通過(guò) Kind() 方法可以獲取 Type 的 Kind),Kind 表示的是 go 底層類型系統(tǒng)中的類型。

比如下面的例子:

func?display(path?string,?v?reflect.Value)?{
?switch?v.Kind()?{
?case?reflect.Invalid:
??fmt.Printf("%s?=?invalid\n",?path)
?case?reflect.Slice,?reflect.Array:
??for?i?:=?0;?i?<?v.Len();?i++?{
???display(fmt.Sprintf("%s[%d]",?path,?i),?v.Index(i))
??}
?case?reflect.Struct:
??for?i?:=?0;?i?<?v.NumField();?i++?{
???fieldPath?:=?fmt.Sprintf("%s.%s",?path,?v.Type().Field(i).Name)
???display(fieldPath,?v.Field(i))
??}
?case?reflect.Map:
??for?_,?key?:=?range?v.MapKeys()?{
???display(fmt.Sprintf("%s[%s]",?path,?formatAny(key)),?v.MapIndex(key))
??}
?case?reflect.Pointer:
??if?v.IsNil()?{
???fmt.Printf("%s?=?nil\n",?path)
??}?else?{
???display(fmt.Sprintf("(*%s)",?path),?v.Elem())
??}
?case?reflect.Interface:
??if?v.IsNil()?{
???fmt.Printf("%s?=?nil\n",?path)
??}?else?{
???fmt.Printf("%s.type?=?%s\n",?path,?v.Elem().Type())
???display(path+".value",?v.Elem())
??}
?default:
??fmt.Printf("%s?=?%s\n",?path,?formatAny(v))
?}
}

我們?cè)陂_(kāi)發(fā)的時(shí)候非常常用的結(jié)構(gòu)體,在 go 的類型系統(tǒng)中,通通都是 Struct 這種類型的。

addressable

go 反射中最后一個(gè)很重要的話題是 addressable。在 go 的反射系統(tǒng)中有兩個(gè)關(guān)于尋址的方法:CanAddrCanSet。

CanAddr 方法的作用是判斷反射對(duì)象是否可以尋址,也就是說(shuō),如果 CanAddr 返回 true,那么我們就可以通過(guò) Addr 方法來(lái)獲取反射對(duì)象的地址。 如果 CanAddr 返回 false,那么我們就不能通過(guò) Addr 方法來(lái)獲取反射對(duì)象的地址。對(duì)于這種情況,我們就無(wú)法通過(guò)反射對(duì)象來(lái)修改變量的值。

但是,CanAddrtrue 并不是說(shuō) reflect.Value 一定就能修改變量的值了。reflect.Value 還有一個(gè)方法 CanSet,只有 CanSet 返回 true,我們才能通過(guò)反射對(duì)象來(lái)修改變量的值。

那么 CanAddr 背后的含義是什么呢?它意味著我們傳遞給 reflect.ValueOf 的變量是不是可以尋址的。也就是說(shuō),我們的反射值對(duì)象拿到的是不是變量本身,而不是變量的副本。如果我們是通過(guò) &v 這種方式來(lái)創(chuàng)建反射對(duì)象的,那么 CanAddr 就會(huì)返回 true, 反之,如果我們是通過(guò) v 這種方式來(lái)創(chuàng)建反射對(duì)象的,那么 CanAddr 就會(huì)返回 false

獲取類型信息 - reflect.Type

概述

reflect.Type 是一個(gè)接口,它代表了一個(gè)類型。我們可以通過(guò) reflect.TypeOf 來(lái)獲取一個(gè)類型的 reflect.Type 對(duì)象。 我們使用 reflect.Type 的目的通常是為了獲取類型的信息,比如類型是什么、類型的名稱、類型的字段、類型的方法等等。 又或者最常見(jiàn)的場(chǎng)景:結(jié)構(gòu)體中的 jsontag,它是沒(méi)有語(yǔ)義的,它的作用就是為了在序列化的時(shí)候,生成我們想要的字段名。 而這個(gè) tag 就是需要通過(guò)反射來(lái)獲取的。

通用的 Type 方法

在 go 的反射系統(tǒng)中,是使用 reflect.Type 這個(gè)接口來(lái)獲取類型信息的。reflect.Type 這個(gè)接口有很多方法,下面這些方法是所有的類型通用的方法:

//?Type?是?Go?類型的表示。
//
//?并非所有方法都適用于所有類型。
//?在調(diào)用?kind?具體方法之前,先使用?Kind?方法找出類型的種類。因?yàn)檎{(diào)用一個(gè)方法如果類型不匹配會(huì)導(dǎo)致?panic
//
//?Type?類型值是可以比較的,比如用?==?操作符。所以它可以用做?map?的?key
//?如果兩個(gè)?Type?值代表相同的類型,那么它們一定是相等的。
type?Type?interface?{
?//?Align?返回該類型在內(nèi)存中分配時(shí),以字節(jié)數(shù)為單位的字節(jié)數(shù)
?Align()?int
?
?//?FieldAlign?返回該類型在結(jié)構(gòu)中作為字段使用時(shí),以字節(jié)數(shù)為單位的字節(jié)數(shù)
?FieldAlign()?int
?
?//?Method?這個(gè)方法返回類型方法集中的第?i?個(gè)方法。
?//?如果?i?不在[0,?NumMethod()]范圍內(nèi),就會(huì)?panic。
?//?對(duì)于非接口類型?T?或?*T,返回的?Method?的?Type?和?Func?字段描述了一個(gè)函數(shù),
?//?其第一個(gè)參數(shù)是接收者,并且只能訪問(wèn)導(dǎo)出的方法。
?//?對(duì)于一個(gè)接口類型,返回的?Method?的?Type?字段給出的是方法簽名,沒(méi)有接收者,F(xiàn)unc字段為nil。
?//?方法是按字典序順序排列的。
?Method(int)?Method

?//?MethodByName?返回類型的方法集中具有該名稱的方法和一個(gè)指示是否找到該方法的布爾值。
?//?對(duì)于非接口類型?T?或?*T,返回的?Method?的?Type?和?Func?字段描述了一個(gè)函數(shù),
?//?其第一個(gè)參數(shù)是接收者。
?//?對(duì)于一個(gè)接口類型,返回的?Method?的?Type?字段給出的是方法簽名,沒(méi)有接收者,F(xiàn)unc字段為nil。
?MethodByName(string)?(Method,?bool)

?//?NumMethod?返回使用?Method?可以訪問(wèn)的方法數(shù)量。
?//?對(duì)于非接口類型,它返回導(dǎo)出方法的數(shù)量。
?//?對(duì)于接口類型,它返回導(dǎo)出和未導(dǎo)出方法的數(shù)量。
?NumMethod()?int

?//?Name?返回定義類型在其包中的類型名稱。
?//?對(duì)于其他(未定義的)類型,它返回空字符串。
?Name()?string

?//?PkgPath?返回一個(gè)定義類型的包的路徑,也就是導(dǎo)入路徑,導(dǎo)入路徑是唯一標(biāo)識(shí)包的類型,如?"encoding/base64"。
?//?如果類型是預(yù)先聲明的(string,?error)或者沒(méi)有定義(*T,?struct{},?[]int,或?A,其中?A?是一個(gè)非定義類型的別名),包的路徑將是空字符串。
?PkgPath()?string

?//?Size?返回存儲(chǔ)給定類型的值所需的字節(jié)數(shù)。它類似于?unsafe.Sizeof.
?Size()?uintptr

?//?String?返回該類型的字符串表示。
?//?字符串表示法可以使用縮短的包名。
?//?(例如,使用?base64?而不是?"encoding/base64")并且它并不能保證類型之間是唯一的。如果是為了測(cè)試類型標(biāo)識(shí),應(yīng)該直接比較類型?Type。
?String()?string

?//?Kind?返回該類型的具體種類。
?Kind()?Kind

?//?Implements?表示該類型是否實(shí)現(xiàn)了接口類型?u。
?Implements(u?Type)?bool

?//?AssignableTo?表示該類型的值是否可以分配給類型?u。
?AssignableTo(u?Type)?bool

?//?ConvertibleTo?表示該類型的值是否可轉(zhuǎn)換為?u?類型。
?ConvertibleTo(u?Type)?bool

?//?Comparable?表示該類型的值是否具有可比性。
?Comparable()?bool
}

某些類型特定的 Type 方法

下面是某些類型特定的方法,對(duì)于這些方法,如果我們使用的類型不對(duì),則會(huì) panic

type?Type?interface?{
?//?Bits?以?bits?為單位返回類型的大小。
?//?如果類型的?Kind?不屬于:sized?或者?unsized?Int,?Uint,?Float,?或者?Complex,會(huì)?panic。
?Bits()?int

?//?ChanDir?返回一個(gè)通道類型的方向。
?//?如果類型的?Kind?不是?Chan,會(huì)?panic。
?ChanDir()?ChanDir

?//?IsVariadic?表示一個(gè)函數(shù)類型的最終輸入?yún)?shù)是否為一個(gè)?"..."?可變參數(shù)。如果是,t.In(t.NumIn()?-?1)?返回參數(shù)的隱式實(shí)際類型?[]T.
?//?更具體的,如果?t?代表?func(x?int,?y?...?float64),那么:
?//?t.NumIn()?==?2
?//?t.In(0)是?"int"?的?reflect.Type?反射類型。
?//?t.In(1)是?"[]float64"?的?reflect.Type?反射類型。
?//?t.IsVariadic()?==?true
?//?如果類型的?Kind?不是?Func,IsVariadic?會(huì)?panic
?IsVariadic()?bool

?//?Elem?返回一個(gè)?type?的元素類型。
?//?如果類型的?Kind?不是?Array、Chan、Map、Ptr?或?Slice,就會(huì)?panic
?Elem()?Type

?//?Field?返回一個(gè)結(jié)構(gòu)類型的第?i?個(gè)字段。
?//?如果類型的?Kind?不是?Struct,就會(huì)?panic。
?//?如果?i?不在?[0,?NumField())?范圍內(nèi)也會(huì)?panic。
?Field(i?int)?StructField

?//?FieldByIndex?返回索引序列對(duì)應(yīng)的嵌套字段。它相當(dāng)于對(duì)每一個(gè)?index?調(diào)用?Field。
?//?如果類型的?Kind?不是?Struct,就會(huì)?panic。
?FieldByIndex(index?[]int)?StructField

?//?FieldByName?返回給定名稱的結(jié)構(gòu)字段和一個(gè)表示是否找到該字段的布爾值。
?FieldByName(name?string)?(StructField,?bool)

?//?FieldByNameFunc?返回一個(gè)能滿足?match?函數(shù)的帶有名稱的?field?字段。布爾值表示是否找到。
?FieldByNameFunc(match?func(string)?bool)?(StructField,?bool)

?//?In?返回函數(shù)類型的第?i?個(gè)輸入?yún)?shù)的類型。
?//?如果類型的?Kind?不是?Func?類型會(huì)?panic。
?//?如果?i?不在?[0,?NumIn())?的范圍內(nèi),會(huì)?panic。
?In(i?int)?Type

?//?Key?返回一個(gè)?map?類型的?key?類型。
?//?如果類型的?Kind?不是?Map,會(huì)?panic。
?Key()?Type

?//?Len?返回一個(gè)數(shù)組類型的長(zhǎng)度。
?//?如果類型的?Kind?不是?Array,會(huì)?panic。
?Len()?int

?//?NumField?返回一個(gè)結(jié)構(gòu)類型的字段數(shù)目。
?//?如果類型的?Kind?不是?Struct,會(huì)?panic。
?NumField()?int

?//?NumIn?返回一個(gè)函數(shù)類型的輸入?yún)?shù)數(shù)。
?//?如果類型的?Kind?不是Func.NumIn(),會(huì)?panic。
?NumIn()?int

?//?NumOut?返回一個(gè)函數(shù)類型的輸出參數(shù)數(shù)。
?//?如果類型的?Kind?不是?Func.NumOut(),會(huì)?panic。
?NumOut()?int

?//?Out?返回一個(gè)函數(shù)類型的第?i?個(gè)輸出參數(shù)的類型。
?//?如果類型的?Kind?不是?Func,會(huì)?panic。
?//?如果?i?不在?[0,?NumOut())?的范圍內(nèi),會(huì)?panic。
?Out(i?int)?Type
}

創(chuàng)建 reflect.Type 的方式

我們可以通過(guò)下面的方式來(lái)獲取變量的類型信息(創(chuàng)建 reflect.Type 的方式):

獲取值信息 - reflect.Value

概述

reflect.Value 是一個(gè)結(jié)構(gòu)體,它代表了一個(gè)值。 我們使用 reflect.Value 可以實(shí)現(xiàn)一些接收多種類型參數(shù)的函數(shù),又或者可以讓我們?cè)谶\(yùn)行時(shí)針對(duì)值的一些信息來(lái)進(jìn)行修改。 常常用在接收 interface{} 類型參數(shù)的方法中,因?yàn)閰?shù)是接口類型,所以我們可以通過(guò) reflect.ValueOf 來(lái)獲取到參數(shù)的值信息。 比如類型、大小、結(jié)構(gòu)體字段、方法等等。

同時(shí),我們可以對(duì)這些獲取到的反射值進(jìn)行修改。這也是反射的一個(gè)重要用途。

reflect.Value 的方法

reflect.Value 這個(gè) Sreuct 同樣有很多方法:具體可以分為以下幾類:

  • 設(shè)置值的方法:Set*Set、SetBool、SetBytesSetCapSetComplexSetFloat、SetIntSetLenSetMapIndex、SetPointer、SetString、SetUint。通過(guò)這類方法,我們可以修改反射值的內(nèi)容,前提是這個(gè)反射值得是合適的類型。CanSet 返回 true 才能調(diào)用這類方法
  • 獲取值的方法:InterfaceInterfaceData、Bool、Bytes、Complex、FloatIntString、Uint。通過(guò)這類方法,我們可以獲取反射值的內(nèi)容。前提是這個(gè)反射值是合適的類型,比如我們不能通過(guò) complex 反射值來(lái)調(diào)用 Int 方法(我們可以通過(guò) Kind 來(lái)判斷類型)。
  • map 類型的方法:MapIndex、MapKeysMapRangeMapSet。
  • chan 類型的方法:CloseRecvSend、TryRecv、TrySend。
  • slice 類型的方法:Len、CapIndexSlice、Slice3
  • struct 類型的方法:NumFieldNumMethod、Field、FieldByIndex、FieldByNameFieldByNameFunc。
  • 判斷是否可以設(shè)置為某一類型:CanConvert、CanComplexCanFloat、CanInt、CanInterface、CanUint。
  • 方法類型的方法:MethodMethodByName、Call、CallSlice
  • 判斷值是否有效:IsValid。
  • 判斷值是否是 nilIsNil。
  • 判斷值是否是零值:IsZero。
  • 判斷值能否容納下某一類型的值:Overflow、OverflowComplexOverflowFloat、OverflowInt、OverflowUint。
  • 反射值指針相關(guān)的方法:AddrCanAddrtrue 才能調(diào)用)、UnsafeAddr、Pointer、UnsafePointer。
  • 獲取類型信息:Type、Kind。
  • 獲取指向元素的值:Elem。
  • 類型轉(zhuǎn)換:Convert

Len 也適用于 slice、array、chan、map、string 類型的反射值。

創(chuàng)建 reflect.Value 的方式

我們可以通過(guò)下面的方式來(lái)獲取變量的值信息(創(chuàng)建 reflect.Value 的方式):

總結(jié)

reflect 包提供了反射機(jī)制,可以在運(yùn)行時(shí)獲取變量的類型信息、值信息、方法信息等等。

go 中的 interface{} 實(shí)際上包含了兩個(gè)指針,一個(gè)指向類型信息,一個(gè)指向值信息。正因如此,我們可以在運(yùn)行時(shí)通過(guò) interface{} 來(lái)獲取變量的類型信息、值信息。

reflect.Type 代表一個(gè)類型,reflect.Value 代表一個(gè)值。通過(guò) reflect.Type 可以獲取類型信息,通過(guò) reflect.Value 可以獲取值信息。

反射三定律:

  • 反射可以將 interface 類型變量轉(zhuǎn)換成反射對(duì)象。
  • 反射可以將反射對(duì)象還原成 interface 對(duì)象。
  • 如果要修改反射對(duì)象,那么反射對(duì)象必須是可設(shè)置的(CanSet)。

reflect.Valuereflect.Type 里面都有 Elem 方法,但是它們的作用不一樣:

  • reflect.TypeElem 方法返回的是元素類型,只適用于 array、chan、map、pointer 和 slice 類型的 reflect.Type。
  • reflect.ValueElem 方法返回的是值,只適用于接口或指針類型的 reflect.Value

通過(guò) reflect.ValueInterface 方法可以獲取到反射對(duì)象的原始變量,但是是 interface{} 類型的。

TypeKind 都表示類型,但是 Type 是類型的反射對(duì)象,Kind 是 go 類型系統(tǒng)中最基本的一些類型,比如 int、stringstruct 等等。

如果我們想通過(guò) reflect.Value 來(lái)修改變量的值,那么 reflect.Value 必須是可設(shè)置的(CanSet)。同時(shí)如果想要 CanSet 為 true,那么我們的變量必須是可尋址的。

我們有很多方法可以創(chuàng)建 reflect.Typereflect.Value,我們需要根據(jù)具體的場(chǎng)景來(lái)選擇合適的方法。

reflect.Typereflect.Value 里面,都有一部分方法是通用的,也有一部分只適用于特定的類型。如果我們想要調(diào)用那些適用于特定類型的方法,那么我們必須先判斷 reflect.Typereflect.Value 的類型(這里說(shuō)的是 Kind),然后再調(diào)用。

原文鏈接:https://mp.weixin.qq.com/s/MuZYTSC6Q4YSYgmyAojzIw

欄目分類
最近更新