網(wǎng)站首頁(yè) 編程語(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.Type
和 reflect.Value
。 它們分別代表了 go 語(yǔ)言中的類型和值。我們可以通過(guò) reflect.TypeOf
和 reflect.ValueOf
來(lái)獲取到一個(gè)變量的類型和值。
var?a?=?1 t?:=?reflect.TypeOf(a) var?b?=?"hello" t1?:=?reflect.ValueOf(b)
我們?nèi)タ匆幌?TypeOf
和 ValueOf
的源碼會(huì)發(fā)現(xiàn),這兩個(gè)方法都接收一個(gè) interface{}
類型的參數(shù),然后返回一個(gè) reflect.Type
和 reflect.Value
類型的值。這也就是為什么我們可以通過(guò) reflect.TypeOf
和 reflect.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.Type
和 reflect.Value
,我們可以通過(guò) reflect.TypeOf
和 reflect.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.Value
和 reflect.Type
這兩個(gè)反射對(duì)象都有 Elem
方法,既然是不同的對(duì)象,那么它們的作用自然是不一樣的。
reflect.Value 的 Elem 方法
reflect.Value
的 Elem
方法的作用是獲取指針指向的值,或者獲取接口的動(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ù)部分(也就是 iface
或 eface
中的 data
字段)。
指針類型:
接口類型:
reflect.Type 的 Elem 方法
reflect.Type
的 Elem
方法的作用是獲取數(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.Value
的 Interface
方法的作用是獲取反射對(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.Type
的 Kind
來(lái)進(jìn)行類型判斷。 也就是說(shuō),我們?cè)谕ㄟ^(guò)反射來(lái)判斷變量的類型的時(shí)候,只需要枚舉 Kind
中的類型,然后通過(guò) reflect.Type
的 Kind
方法來(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)于尋址的方法:CanAddr
和 CanSet
。
CanAddr
方法的作用是判斷反射對(duì)象是否可以尋址,也就是說(shuō),如果 CanAddr
返回 true
,那么我們就可以通過(guò) Addr
方法來(lái)獲取反射對(duì)象的地址。 如果 CanAddr
返回 false
,那么我們就不能通過(guò) Addr
方法來(lái)獲取反射對(duì)象的地址。對(duì)于這種情況,我們就無(wú)法通過(guò)反射對(duì)象來(lái)修改變量的值。
但是,CanAddr
是 true
并不是說(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)體中的 json
的 tag
,它是沒(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
、SetBytes
、SetCap
、SetComplex
、SetFloat
、SetInt
、SetLen
、SetMapIndex
、SetPointer
、SetString
、SetUint
。通過(guò)這類方法,我們可以修改反射值的內(nèi)容,前提是這個(gè)反射值得是合適的類型。CanSet 返回 true 才能調(diào)用這類方法 - 獲取值的方法:
Interface
、InterfaceData
、Bool
、Bytes
、Complex
、Float
、Int
、String
、Uint
。通過(guò)這類方法,我們可以獲取反射值的內(nèi)容。前提是這個(gè)反射值是合適的類型,比如我們不能通過(guò)complex
反射值來(lái)調(diào)用Int
方法(我們可以通過(guò)Kind
來(lái)判斷類型)。 - map 類型的方法:
MapIndex
、MapKeys
、MapRange
、MapSet
。 - chan 類型的方法:
Close
、Recv
、Send
、TryRecv
、TrySend
。 - slice 類型的方法:
Len
、Cap
、Index
、Slice
、Slice3
。 - struct 類型的方法:
NumField
、NumMethod
、Field
、FieldByIndex
、FieldByName
、FieldByNameFunc
。 - 判斷是否可以設(shè)置為某一類型:
CanConvert
、CanComplex
、CanFloat
、CanInt
、CanInterface
、CanUint
。 - 方法類型的方法:
Method
、MethodByName
、Call
、CallSlice
。 - 判斷值是否有效:
IsValid
。 - 判斷值是否是
nil
:IsNil
。 - 判斷值是否是零值:
IsZero
。 - 判斷值能否容納下某一類型的值:
Overflow
、OverflowComplex
、OverflowFloat
、OverflowInt
、OverflowUint
。 - 反射值指針相關(guān)的方法:
Addr
(CanAddr
為true
才能調(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.Value
和 reflect.Type
里面都有 Elem
方法,但是它們的作用不一樣:
-
reflect.Type
的Elem
方法返回的是元素類型,只適用于 array、chan、map、pointer 和 slice 類型的reflect.Type
。 -
reflect.Value
的Elem
方法返回的是值,只適用于接口或指針類型的reflect.Value
。
通過(guò) reflect.Value
的 Interface
方法可以獲取到反射對(duì)象的原始變量,但是是 interface{}
類型的。
Type
和 Kind
都表示類型,但是 Type
是類型的反射對(duì)象,Kind
是 go 類型系統(tǒng)中最基本的一些類型,比如 int
、string
、struct
等等。
如果我們想通過(guò) reflect.Value
來(lái)修改變量的值,那么 reflect.Value
必須是可設(shè)置的(CanSet
)。同時(shí)如果想要 CanSet
為 true,那么我們的變量必須是可尋址的。
我們有很多方法可以創(chuàng)建 reflect.Type
和 reflect.Value
,我們需要根據(jù)具體的場(chǎng)景來(lái)選擇合適的方法。
reflect.Type
和 reflect.Value
里面,都有一部分方法是通用的,也有一部分只適用于特定的類型。如果我們想要調(diào)用那些適用于特定類型的方法,那么我們必須先判斷 reflect.Type
或 reflect.Value
的類型(這里說(shuō)的是 Kind
),然后再調(diào)用。
原文鏈接:https://mp.weixin.qq.com/s/MuZYTSC6Q4YSYgmyAojzIw
相關(guān)推薦
- 2022-07-08 android?studio集成unity導(dǎo)出工程的實(shí)現(xiàn)_Android
- 2023-02-04 詳解如何在C#中接受或拒絕Excel中的修訂_C#教程
- 2022-07-22 比較兩個(gè)對(duì)象屬性值是否相同方法
- 2024-04-06 elasticsearch(es)高級(jí)查詢api
- 2022-06-24 C#利用itext實(shí)現(xiàn)PDF頁(yè)面處理與切分_C#教程
- 2023-01-15 C++實(shí)現(xiàn)自定義撤銷重做功能的示例代碼_C 語(yǔ)言
- 2022-02-12 asp.net 報(bào)錯(cuò) “/”應(yīng)用程序中的服務(wù)器錯(cuò)誤。 String or binary data w
- 2022-03-24 C++關(guān)于指針,繼承和多態(tài)介紹_C 語(yǔ)言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支