深入了解Golang中reflect反射基本原理
反射概述
反射是這樣一種機(jī)制,它是可以讓我們在程序運行時(runtime)訪問、檢測和修改對象本身狀態(tài)或行為的一種能力。 比如,從一個變量推斷出其類型信息、以及存儲的數(shù)據(jù)的一些信息,又或者獲取一個對象有什么方法可以調(diào)用等。 反射經(jīng)常用在一些需要同時處理不同類型變量的地方,比如序列化、反序列化、ORM
等等,如標(biāo)準(zhǔn)庫里面的 json.Marshal
。
反射基礎(chǔ) - go 的 interface 是怎么存儲的
在正式開始講解反射之前,我們有必要了解一下 go 里的接口(interface
)是怎么存儲的。 關(guān)于這個問題,在我的另外一篇文章中已經(jīng)做了很詳細(xì)的講解 go interface 設(shè)計與實現(xiàn), 這里不再贅述。但還是簡單說一下,go 的接口是由兩部分組成的,一部分是類型信息,另一部分是數(shù)據(jù)信息,如:
var?a?=?1 var?b?interface{}?=?a
對于這個例子,b
的類型信息是 int
,數(shù)據(jù)信息是 1
,這兩部分信息都是存儲在 b
里面的。b
的內(nèi)存結(jié)構(gòu)如下:
在上圖中,b
的類型實際上是 eface
,它是一個空接口,它的定義如下:
type?eface?struct?{ ????_type?*_type ????data??unsafe.Pointer }
也就是說,一個 interface{} 中實際上既包含了變量的類型信息,也包含了類型的數(shù)據(jù)。正因為如此,我們才可以通過反射來獲取到變量的類型信息,以及變量的數(shù)據(jù)信息。
反射對象 - reflect.Type 和 reflect.Value
知道了 interface{}
的內(nèi)存結(jié)構(gòu)之后,我們就可以開始講解反射了。反射的核心是兩個對象,分別是 reflect.Type
和 reflect.Value
。 它們分別代表了 go 語言中的類型和值。我們可以通過 reflect.TypeOf
和 reflect.ValueOf
來獲取到一個變量的類型和值。
var?a?=?1 t?:=?reflect.TypeOf(a) var?b?=?"hello" t1?:=?reflect.ValueOf(b)
我們?nèi)タ匆幌?TypeOf
和 ValueOf
的源碼會發(fā)現(xiàn),這兩個方法都接收一個 interface{}
類型的參數(shù),然后返回一個 reflect.Type
和 reflect.Value
類型的值。這也就是為什么我們可以通過 reflect.TypeOf
和 reflect.ValueOf
來獲取到一個變量的類型和值的原因。
反射定律
在 go 官方博客中關(guān)于反射的文章 laws-of-reflection 中,提到了三條反射定律:
- 反射可以將
interface
類型變量轉(zhuǎn)換成反射對象。 - 反射可以將反射對象還原成
interface
對象。 - 如果要修改反射對象,那么反射對象必須是可設(shè)置的(
CanSet
)。
關(guān)于這三條定律,官方博客已經(jīng)有了比較完整的闡述,感興趣的可以去看一下官方博客的文章。這里簡單闡述一下:
反射可以將 interface 類型變量轉(zhuǎn)換成反射對象。
其實也就是上面的 reflect.Type
和 reflect.Value
,我們可以通過 reflect.TypeOf
和 reflect.ValueOf
來獲取到一個變量的反射類型和反射值。
var?a?=?1 typeOfA?:=?reflect.TypeOf(a) valueOfA?:=?reflect.ValueOf(a)
反射可以將反射對象還原成 interface 對象。
我們可以通過 reflect.Value.Interface
來獲取到反射對象的 interface
對象,也就是傳遞給 reflect.ValueOf
的那個變量本身。 不過返回值類型是 interface{}
,所以我們需要進(jìn)行類型斷言。
i?:=?valueOfA.Interface() fmt.Println(i.(int))
如果要修改反射對象,那么反射對象必須是可設(shè)置的(CanSet)。
我們可以通過 reflect.Value.CanSet
來判斷一個反射對象是否是可設(shè)置的。如果是可設(shè)置的,我們就可以通過 reflect.Value.Set
來修改反射對象的值。 這其實也是非常場景的使用反射的一個場景,通過反射來修改變量的值。
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
那什么情況下一個反射對象是可設(shè)置的呢?前提是這個反射對象是一個指針,然后這個指針指向的是一個可設(shè)置的變量。 在我們傳遞一個值給 reflect.ValueOf
的時候,如果這個值只是一個普通的變量,那么 reflect.ValueOf
會返回一個不可設(shè)置的反射對象。 因為這個值實際上被拷貝了一份,我們?nèi)绻ㄟ^反射修改這個值,那么實際上是修改的這個拷貝的值,而不是原來的值。 所以 go 語言在這里做了一個限制,如果我們傳遞進(jìn) reflect.ValueOf
的變量是一個普通的變量,那么在我們設(shè)置反射對象的值的時候,會報錯。 所以在上面這個例子中,我們傳遞了 x
的指針變量作為參數(shù)。這樣,運行時就可以找到 x
本身,而不是 x
的拷貝,所以就可以修改 x
的值了。
但同時我們也注意到了,在上面這個例子中,v.CanSet()
返回的是 false
,而 v.Elem().CanSet()
返回的是 true
。 這是因為,v
是一個指針,而 v.Elem()
是指針指向的值,對于這個指針本身,我們修改它是沒有意義的,我們可以設(shè)想一下, 如果我們修改了指針變量(也就是修改了指針變量指向的地址),那會發(fā)生什么呢?那樣我們的指針變量就不是指向 x
了, 而是指向了其他的變量,這樣就不符合我們的預(yù)期了。所以 v.CanSet()
返回的是 false
。
而 v.Elem().CanSet()
返回的是 true
。這是因為 v.Elem()
才是 x
本身,通過 v.Elem()
修改 x
的值是沒有問題的。
Elem 方法
不知道有多少讀者和我一樣,在初次使用 go 的反射的時候,被 Elem
這個方法搞得一頭霧水。Elem
方法的作用是什么呢?在回答這個問題之前,我們需要明確一點:reflect.Value
和 reflect.Type
這兩個反射對象都有 Elem
方法,既然是不同的對象,那么它們的作用自然是不一樣的。
reflect.Value 的 Elem 方法
reflect.Value
的 Elem
方法的作用是獲取指針指向的值,或者獲取接口的動態(tài)值。也就是說,能調(diào)用 Elem
方法的反射對象,必須是一個指針或者一個接口。 在使用其他類型的 reflect.Value
來調(diào)用 Elem
方法的時候,會 panic
:
var?a?=?1 //?panic:?reflect:?call?of?reflect.Value.Elem?on?int?Value reflect.ValueOf(a).Elem() //?不報錯 var?b?=?&a reflect.ValueOf(b).Elem()
對于指針很好理解,其實作用類似解引用。而對于接口,還是要回到 interface
的結(jié)構(gòu)本身,因為接口里包含了類型和數(shù)據(jù)本身,所以 Elem
方法就是獲取接口的數(shù)據(jù)部分(也就是 iface
或 eface
中的 data
字段)。
指針類型:
接口類型:
reflect.Type 的 Elem 方法
reflect.Type
的 Elem
方法的作用是獲取數(shù)組、chan、map、指針、切片關(guān)聯(lián)元素的類型信息,也就是說,對于 reflect.Type
來說, 能調(diào)用 Elem
方法的反射對象,必須是數(shù)組、chan、map、指針、切片中的一種,其他類型的 reflect.Type
調(diào)用 Elem
方法會 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 方法
這也是非常常用的一個方法,reflect.Value
的 Interface
方法的作用是獲取反射對象的動態(tài)值。 也就是說,如果反射對象是一個指針,那么 Interface
方法會返回指針指向的值。
簡單來說,如果 var i interface{} = x
,那么 reflect.ValueOf(x).Interface()
就是 i
本身,只不過其類型是 interface{}
類型。
Kind
說到反射,不得不提的另外一個話題就是 go 的類型系統(tǒng),對于開發(fā)者來說,我們可以基于基本類型來定義各種新的類型,如:
//?Kind?是?int type?myIny?int //?Kind?是?Struct type?Person?struct?{ ????Name?string ????Age?int }
但是不管我們定義了多少種類型,在 go 看來都是下面的基本類型中的一個:
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 )
也就是說,我們定義的類型在 go 的類型系統(tǒng)中都是基本類型的一種,這個基本類型就是 Kind
。 也正因為如此,我們可以通過有限的 reflect.Type
的 Kind
來進(jìn)行類型判斷。 也就是說,我們在通過反射來判斷變量的類型的時候,只需要枚舉 Kind
中的類型,然后通過 reflect.Type
的 Kind
方法來判斷即可。
Type 表示的是反射對象(Type 對象是某一個 Kind,通過 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)) ?} }
我們在開發(fā)的時候非常常用的結(jié)構(gòu)體,在 go 的類型系統(tǒng)中,通通都是 Struct
這種類型的。
addressable
go 反射中最后一個很重要的話題是 addressable
。在 go 的反射系統(tǒng)中有兩個關(guān)于尋址的方法:CanAddr
和 CanSet
。
CanAddr
方法的作用是判斷反射對象是否可以尋址,也就是說,如果 CanAddr
返回 true
,那么我們就可以通過 Addr
方法來獲取反射對象的地址。 如果 CanAddr
返回 false
,那么我們就不能通過 Addr
方法來獲取反射對象的地址。對于這種情況,我們就無法通過反射對象來修改變量的值。
但是,CanAddr
是 true
并不是說 reflect.Value
一定就能修改變量的值了。reflect.Value
還有一個方法 CanSet
,只有 CanSet
返回 true
,我們才能通過反射對象來修改變量的值。
那么 CanAddr
背后的含義是什么呢?它意味著我們傳遞給 reflect.ValueOf
的變量是不是可以尋址的。也就是說,我們的反射值對象拿到的是不是變量本身,而不是變量的副本。如果我們是通過 &v
這種方式來創(chuàng)建反射對象的,那么 CanAddr
就會返回 true
, 反之,如果我們是通過 v
這種方式來創(chuàng)建反射對象的,那么 CanAddr
就會返回 false
。
獲取類型信息 - reflect.Type
概述
reflect.Type
是一個接口,它代表了一個類型。我們可以通過 reflect.TypeOf
來獲取一個類型的 reflect.Type
對象。 我們使用 reflect.Type
的目的通常是為了獲取類型的信息,比如類型是什么、類型的名稱、類型的字段、類型的方法等等。 又或者最常見的場景:結(jié)構(gòu)體中的 json
的 tag
,它是沒有語義的,它的作用就是為了在序列化的時候,生成我們想要的字段名。 而這個 tag
就是需要通過反射來獲取的。
通用的 Type 方法
在 go 的反射系統(tǒng)中,是使用 reflect.Type
這個接口來獲取類型信息的。reflect.Type
這個接口有很多方法,下面這些方法是所有的類型通用的方法:
//?Type?是?Go?類型的表示。 // //?并非所有方法都適用于所有類型。 //?在調(diào)用?kind?具體方法之前,先使用?Kind?方法找出類型的種類。因為調(diào)用一個方法如果類型不匹配會導(dǎo)致?panic // //?Type?類型值是可以比較的,比如用?==?操作符。所以它可以用做?map?的?key //?如果兩個?Type?值代表相同的類型,那么它們一定是相等的。 type?Type?interface?{ ?//?Align?返回該類型在內(nèi)存中分配時,以字節(jié)數(shù)為單位的字節(jié)數(shù) ?Align()?int ? ?//?FieldAlign?返回該類型在結(jié)構(gòu)中作為字段使用時,以字節(jié)數(shù)為單位的字節(jié)數(shù) ?FieldAlign()?int ? ?//?Method?這個方法返回類型方法集中的第?i?個方法。 ?//?如果?i?不在[0,?NumMethod()]范圍內(nèi),就會?panic。 ?//?對于非接口類型?T?或?*T,返回的?Method?的?Type?和?Func?字段描述了一個函數(shù), ?//?其第一個參數(shù)是接收者,并且只能訪問導(dǎo)出的方法。 ?//?對于一個接口類型,返回的?Method?的?Type?字段給出的是方法簽名,沒有接收者,F(xiàn)unc字段為nil。 ?//?方法是按字典序順序排列的。 ?Method(int)?Method ?//?MethodByName?返回類型的方法集中具有該名稱的方法和一個指示是否找到該方法的布爾值。 ?//?對于非接口類型?T?或?*T,返回的?Method?的?Type?和?Func?字段描述了一個函數(shù), ?//?其第一個參數(shù)是接收者。 ?//?對于一個接口類型,返回的?Method?的?Type?字段給出的是方法簽名,沒有接收者,F(xiàn)unc字段為nil。 ?MethodByName(string)?(Method,?bool) ?//?NumMethod?返回使用?Method?可以訪問的方法數(shù)量。 ?//?對于非接口類型,它返回導(dǎo)出方法的數(shù)量。 ?//?對于接口類型,它返回導(dǎo)出和未導(dǎo)出方法的數(shù)量。 ?NumMethod()?int ?//?Name?返回定義類型在其包中的類型名稱。 ?//?對于其他(未定義的)類型,它返回空字符串。 ?Name()?string ?//?PkgPath?返回一個定義類型的包的路徑,也就是導(dǎo)入路徑,導(dǎo)入路徑是唯一標(biāo)識包的類型,如?"encoding/base64"。 ?//?如果類型是預(yù)先聲明的(string,?error)或者沒有定義(*T,?struct{},?[]int,或?A,其中?A?是一個非定義類型的別名),包的路徑將是空字符串。 ?PkgPath()?string ?//?Size?返回存儲給定類型的值所需的字節(jié)數(shù)。它類似于?unsafe.Sizeof. ?Size()?uintptr ?//?String?返回該類型的字符串表示。 ?//?字符串表示法可以使用縮短的包名。 ?//?(例如,使用?base64?而不是?"encoding/base64")并且它并不能保證類型之間是唯一的。如果是為了測試類型標(biāo)識,應(yīng)該直接比較類型?Type。 ?String()?string ?//?Kind?返回該類型的具體種類。 ?Kind()?Kind ?//?Implements?表示該類型是否實現(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 方法
下面是某些類型特定的方法,對于這些方法,如果我們使用的類型不對,則會 panic
:
type?Type?interface?{ ?//?Bits?以?bits?為單位返回類型的大小。 ?//?如果類型的?Kind?不屬于:sized?或者?unsized?Int,?Uint,?Float,?或者?Complex,會?panic。 ?Bits()?int ?//?ChanDir?返回一個通道類型的方向。 ?//?如果類型的?Kind?不是?Chan,會?panic。 ?ChanDir()?ChanDir ?//?IsVariadic?表示一個函數(shù)類型的最終輸入?yún)?shù)是否為一個?"..."?可變參數(shù)。如果是,t.In(t.NumIn()?-?1)?返回參數(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?會?panic ?IsVariadic()?bool ?//?Elem?返回一個?type?的元素類型。 ?//?如果類型的?Kind?不是?Array、Chan、Map、Ptr?或?Slice,就會?panic ?Elem()?Type ?//?Field?返回一個結(jié)構(gòu)類型的第?i?個字段。 ?//?如果類型的?Kind?不是?Struct,就會?panic。 ?//?如果?i?不在?[0,?NumField())?范圍內(nèi)也會?panic。 ?Field(i?int)?StructField ?//?FieldByIndex?返回索引序列對應(yīng)的嵌套字段。它相當(dāng)于對每一個?index?調(diào)用?Field。 ?//?如果類型的?Kind?不是?Struct,就會?panic。 ?FieldByIndex(index?[]int)?StructField ?//?FieldByName?返回給定名稱的結(jié)構(gòu)字段和一個表示是否找到該字段的布爾值。 ?FieldByName(name?string)?(StructField,?bool) ?//?FieldByNameFunc?返回一個能滿足?match?函數(shù)的帶有名稱的?field?字段。布爾值表示是否找到。 ?FieldByNameFunc(match?func(string)?bool)?(StructField,?bool) ?//?In?返回函數(shù)類型的第?i?個輸入?yún)?shù)的類型。 ?//?如果類型的?Kind?不是?Func?類型會?panic。 ?//?如果?i?不在?[0,?NumIn())?的范圍內(nèi),會?panic。 ?In(i?int)?Type ?//?Key?返回一個?map?類型的?key?類型。 ?//?如果類型的?Kind?不是?Map,會?panic。 ?Key()?Type ?//?Len?返回一個數(shù)組類型的長度。 ?//?如果類型的?Kind?不是?Array,會?panic。 ?Len()?int ?//?NumField?返回一個結(jié)構(gòu)類型的字段數(shù)目。 ?//?如果類型的?Kind?不是?Struct,會?panic。 ?NumField()?int ?//?NumIn?返回一個函數(shù)類型的輸入?yún)?shù)數(shù)。 ?//?如果類型的?Kind?不是Func.NumIn(),會?panic。 ?NumIn()?int ?//?NumOut?返回一個函數(shù)類型的輸出參數(shù)數(shù)。 ?//?如果類型的?Kind?不是?Func.NumOut(),會?panic。 ?NumOut()?int ?//?Out?返回一個函數(shù)類型的第?i?個輸出參數(shù)的類型。 ?//?如果類型的?Kind?不是?Func,會?panic。 ?//?如果?i?不在?[0,?NumOut())?的范圍內(nèi),會?panic。 ?Out(i?int)?Type }
創(chuàng)建 reflect.Type 的方式
我們可以通過下面的方式來獲取變量的類型信息(創(chuàng)建 reflect.Type
的方式):
獲取值信息 - reflect.Value
概述
reflect.Value
是一個結(jié)構(gòu)體,它代表了一個值。 我們使用 reflect.Value
可以實現(xiàn)一些接收多種類型參數(shù)的函數(shù),又或者可以讓我們在運行時針對值的一些信息來進(jìn)行修改。 常常用在接收 interface{}
類型參數(shù)的方法中,因為參數(shù)是接口類型,所以我們可以通過 reflect.ValueOf
來獲取到參數(shù)的值信息。 比如類型、大小、結(jié)構(gòu)體字段、方法等等。
同時,我們可以對這些獲取到的反射值進(jìn)行修改。這也是反射的一個重要用途。
reflect.Value 的方法
reflect.Value
這個 Sreuct
同樣有很多方法:具體可以分為以下幾類:
- 設(shè)置值的方法:
Set*
:Set
、SetBool
、SetBytes
、SetCap
、SetComplex
、SetFloat
、SetInt
、SetLen
、SetMapIndex
、SetPointer
、SetString
、SetUint
。通過這類方法,我們可以修改反射值的內(nèi)容,前提是這個反射值得是合適的類型。CanSet 返回 true 才能調(diào)用這類方法 - 獲取值的方法:
Interface
、InterfaceData
、Bool
、Bytes
、Complex
、Float
、Int
、String
、Uint
。通過這類方法,我們可以獲取反射值的內(nèi)容。前提是這個反射值是合適的類型,比如我們不能通過complex
反射值來調(diào)用Int
方法(我們可以通過Kind
來判斷類型)。 - 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 的方式
我們可以通過下面的方式來獲取變量的值信息(創(chuàng)建 reflect.Value
的方式):
總結(jié)
reflect
包提供了反射機(jī)制,可以在運行時獲取變量的類型信息、值信息、方法信息等等。
go 中的 interface{}
實際上包含了兩個指針,一個指向類型信息,一個指向值信息。正因如此,我們可以在運行時通過 interface{}
來獲取變量的類型信息、值信息。
reflect.Type
代表一個類型,reflect.Value
代表一個值。通過 reflect.Type
可以獲取類型信息,通過 reflect.Value
可以獲取值信息。
反射三定律:
- 反射可以將
interface
類型變量轉(zhuǎn)換成反射對象。 - 反射可以將反射對象還原成
interface
對象。 - 如果要修改反射對象,那么反射對象必須是可設(shè)置的(
CanSet
)。
reflect.Value
和 reflect.Type
里面都有 Elem
方法,但是它們的作用不一樣:
reflect.Type
的Elem
方法返回的是元素類型,只適用于 array、chan、map、pointer 和 slice 類型的reflect.Type
。reflect.Value
的Elem
方法返回的是值,只適用于接口或指針類型的reflect.Value
。
通過 reflect.Value
的 Interface
方法可以獲取到反射對象的原始變量,但是是 interface{}
類型的。
Type
和 Kind
都表示類型,但是 Type
是類型的反射對象,Kind
是 go 類型系統(tǒng)中最基本的一些類型,比如 int
、string
、struct
等等。
如果我們想通過 reflect.Value
來修改變量的值,那么 reflect.Value
必須是可設(shè)置的(CanSet
)。同時如果想要 CanSet
為 true,那么我們的變量必須是可尋址的。
我們有很多方法可以創(chuàng)建 reflect.Type
和 reflect.Value
,我們需要根據(jù)具體的場景來選擇合適的方法。
reflect.Type
和 reflect.Value
里面,都有一部分方法是通用的,也有一部分只適用于特定的類型。如果我們想要調(diào)用那些適用于特定類型的方法,那么我們必須先判斷 reflect.Type
或 reflect.Value
的類型(這里說的是 Kind
),然后再調(diào)用。
到此這篇關(guān)于深入了解Golang中reflect反射基本原理的文章就介紹到這了,更多相關(guān)Golang reflect反射原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go從指定的URL下載圖片并保存到本地的代碼實現(xiàn)
這段代碼定義了一個名為 downloadImage 的函數(shù),其目的是從指定的URL下載圖片并保存到本地文件系統(tǒng),本文是對代碼功能的詳細(xì)描述,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-08-08Go中使用單調(diào)時鐘獲得準(zhǔn)確的時間間隔問題
這篇文章主要介紹了Go中使用單調(diào)時鐘獲得準(zhǔn)確的時間間隔,在go語言中,沒有直接調(diào)用時鐘的函數(shù),可以通過?time.Now()?獲得帶單調(diào)時鐘的?Time?結(jié)構(gòu)體,并通過Since和Until獲得相對準(zhǔn)確的時間間隔,需要的朋友可以參考下2022-06-06關(guān)于Golang變量初始化/類型推斷/短聲明的問題
這篇文章主要介紹了關(guān)于Golang變量初始化/類型推斷/短聲明的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02加速開發(fā):使用Go語言和Gin框架構(gòu)建Web項目的利器
Go語言和Gin框架是構(gòu)建高性能Web項目的利器,Go語言的簡潔性和并發(fā)性,以及Gin框架的輕量級和快速路由能力,使開發(fā)者能夠快速構(gòu)建可靠的Web應(yīng)用程序,需要的朋友可以參考下2023-09-09