欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2023年01月04日 12:00:26   作者:rubys_  
反射是這樣一種機(jī)制,它是可以讓我們在程序運(yùn)行時(shí)(runtime)訪問、檢測和修改對象本身狀態(tài)或行為的一種能力。本文主要帶大家來看看Golang中reflect反射基本原理,需要的可以參考一下

反射概述

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

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

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

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

對于這個(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
}

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

反射對象 - reflect.Type 和 reflect.Value

知道了 interface{} 的內(nèi)存結(jié)構(gòu)之后,我們就可以開始講解反射了。反射的核心是兩個(gè)對象,分別是 reflect.Typereflect.Value。 它們分別代表了 go 語言中的類型和值。我們可以通過 reflect.TypeOfreflect.ValueOf 來獲取到一個(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 類型的值。這也就是為什么我們可以通過 reflect.TypeOfreflect.ValueOf 來獲取到一個(gè)變量的類型和值的原因。

反射定律

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

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

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

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

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

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

反射可以將反射對象還原成 interface 對象。

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

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

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

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

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è)反射對象是可設(shè)置的呢?前提是這個(gè)反射對象是一個(gè)指針,然后這個(gè)指針指向的是一個(gè)可設(shè)置的變量。 在我們傳遞一個(gè)值給 reflect.ValueOf 的時(shí)候,如果這個(gè)值只是一個(gè)普通的變量,那么 reflect.ValueOf 會(huì)返回一個(gè)不可設(shè)置的反射對象。 因?yàn)檫@個(gè)值實(shí)際上被拷貝了一份,我們?nèi)绻ㄟ^反射修改這個(gè)值,那么實(shí)際上是修改的這個(gè)拷貝的值,而不是原來的值。 所以 go 語言在這里做了一個(gè)限制,如果我們傳遞進(jìn) reflect.ValueOf 的變量是一個(gè)普通的變量,那么在我們設(shè)置反射對象的值的時(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() 是指針指向的值,對于這個(gè)指針本身,我們修改它是沒有意義的,我們可以設(shè)想一下, 如果我們修改了指針變量(也就是修改了指針變量指向的地址),那會(huì)發(fā)生什么呢?那樣我們的指針變量就不是指向 x 了, 而是指向了其他的變量,這樣就不符合我們的預(yù)期了。所以 v.CanSet() 返回的是 false。

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

Elem 方法

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

reflect.Value 的 Elem 方法

reflect.ValueElem 方法的作用是獲取指針指向的值,或者獲取接口的動(dòng)態(tài)值。也就是說,能調(diào)用 Elem 方法的反射對象,必須是一個(gè)指針或者一個(gè)接口。 在使用其他類型的 reflect.Value 來調(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()

對于指針很好理解,其實(shí)作用類似解引用。而對于接口,還是要回到 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)元素的類型信息,也就是說,對于 reflect.Type 來說, 能調(diào)用 Elem 方法的反射對象,必須是數(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 方法的作用是獲取反射對象的動(dòng)態(tài)值。 也就是說,如果反射對象是一個(gè)指針,那么 Interface 方法會(huì)返回指針指向的值。

簡單來說,如果 var i interface{} = x,那么 reflect.ValueOf(x).Interface() 就是 i 本身,只不過其類型是 interface{} 類型。

Kind

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

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

但是不管我們定義了多少種類型,在 go 看來都是下面的基本類型中的一個(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
)

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

Type 表示的是反射對象(Type 對象是某一個(gè) 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ā)的時(shí)候非常常用的結(jié)構(gòu)體,在 go 的類型系統(tǒng)中,通通都是 Struct 這種類型的。

addressable

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

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

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

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

獲取類型信息 - reflect.Type

概述

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

通用的 Type 方法

在 go 的反射系統(tǒng)中,是使用 reflect.Type 這個(gè)接口來獲取類型信息的。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。
?//?對于非接口類型?T?或?*T,返回的?Method?的?Type?和?Func?字段描述了一個(gè)函數(shù),
?//?其第一個(gè)參數(shù)是接收者,并且只能訪問導(dǎo)出的方法。
?//?對于一個(gè)接口類型,返回的?Method?的?Type?字段給出的是方法簽名,沒有接收者,F(xiàn)unc字段為nil。
?//?方法是按字典序順序排列的。
?Method(int)?Method

?//?MethodByName?返回類型的方法集中具有該名稱的方法和一個(gè)指示是否找到該方法的布爾值。
?//?對于非接口類型?T?或?*T,返回的?Method?的?Type?和?Func?字段描述了一個(gè)函數(shù),
?//?其第一個(gè)參數(shù)是接收者。
?//?對于一個(gè)接口類型,返回的?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?返回一個(gè)定義類型的包的路徑,也就是導(dǎo)入路徑,導(dǎo)入路徑是唯一標(biāo)識(shí)包的類型,如?"encoding/base64"。
?//?如果類型是預(yù)先聲明的(string,?error)或者沒有定義(*T,?struct{},?[]int,或?A,其中?A?是一個(gè)非定義類型的別名),包的路徑將是空字符串。
?PkgPath()?string

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

?//?String?返回該類型的字符串表示。
?//?字符串表示法可以使用縮短的包名。
?//?(例如,使用?base64?而不是?"encoding/base64")并且它并不能保證類型之間是唯一的。如果是為了測試類型標(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 方法

下面是某些類型特定的方法,對于這些方法,如果我們使用的類型不對,則會(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?返回索引序列對應(yīng)的嵌套字段。它相當(dāng)于對每一個(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ù)組類型的長度。
?//?如果類型的?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 的方式

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

獲取值信息 - reflect.Value

概述

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

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

reflect.Value 的方法

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

  • 設(shè)置值的方法:Set*Set、SetBool、SetBytes、SetCapSetComplex、SetFloatSetInt、SetLenSetMapIndex、SetPointerSetStringSetUint。通過這類方法,我們可以修改反射值的內(nèi)容,前提是這個(gè)反射值得是合適的類型。CanSet 返回 true 才能調(diào)用這類方法
  • 獲取值的方法:Interface、InterfaceData、Bool、BytesComplex、Float、Int、String、Uint。通過這類方法,我們可以獲取反射值的內(nèi)容。前提是這個(gè)反射值是合適的類型,比如我們不能通過 complex 反射值來調(diào)用 Int 方法(我們可以通過 Kind 來判斷類型)。
  • map 類型的方法:MapIndex、MapKeys、MapRange、MapSet。
  • chan 類型的方法:Close、Recv、SendTryRecv、TrySend。
  • slice 類型的方法:Len、Cap、Index、Slice、Slice3。
  • struct 類型的方法:NumField、NumMethod、Field、FieldByIndex、FieldByNameFieldByNameFunc。
  • 判斷是否可以設(shè)置為某一類型:CanConvertCanComplex、CanFloat、CanInt、CanInterface、CanUint
  • 方法類型的方法:Method、MethodByName、CallCallSlice。
  • 判斷值是否有效:IsValid。
  • 判斷值是否是 nilIsNil。
  • 判斷值是否是零值:IsZero。
  • 判斷值能否容納下某一類型的值:Overflow、OverflowComplexOverflowFloat、OverflowIntOverflowUint。
  • 反射值指針相關(guān)的方法:AddrCanAddrtrue 才能調(diào)用)、UnsafeAddr、PointerUnsafePointer。
  • 獲取類型信息:Type、Kind
  • 獲取指向元素的值:Elem。
  • 類型轉(zhuǎn)換:Convert

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

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

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

總結(jié)

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

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

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

反射三定律:

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

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

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

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

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

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

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

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

到此這篇關(guān)于深入了解Golang中reflect反射基本原理的文章就介紹到這了,更多相關(guān)Golang reflect反射原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 手把手教你如何在Goland中創(chuàng)建和運(yùn)行項(xiàng)目

    手把手教你如何在Goland中創(chuàng)建和運(yùn)行項(xiàng)目

    歡迎來到本指南!我們將手把手地教您在Goland中如何創(chuàng)建、配置并運(yùn)行項(xiàng)目,通過簡單的步驟,您將迅速上手這款強(qiáng)大的集成開發(fā)環(huán)境(IDE),輕松實(shí)現(xiàn)您的編程夢想,讓我們一起開啟這段精彩的旅程吧!
    2024-02-02
  • Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn)

    Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn)

    本文主要介紹了Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Golang實(shí)踐之Error創(chuàng)建和處理詳解

    Golang實(shí)踐之Error創(chuàng)建和處理詳解

    在 C#、Java 等語言中常常使用 try...catch的方式來捕獲異常,但是在Golang 對于錯(cuò)誤處理有不同的方式,像網(wǎng)上也有很多對 error 處理的最佳實(shí)踐的文章,其中很多其實(shí)就是對 error 的統(tǒng)一封裝,使用規(guī)范進(jìn)行約束,本文主要是記錄自己對處理 Error 的一些認(rèn)識(shí)和學(xué)習(xí)
    2023-09-09
  • Golang?throttled基于GCRA速率限制庫使用探索

    Golang?throttled基于GCRA速率限制庫使用探索

    這篇文章主要為大家介紹了Golang?throttled基于GCRA速率限制庫使用實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • beego獲取ajax數(shù)據(jù)的實(shí)例

    beego獲取ajax數(shù)據(jù)的實(shí)例

    下面小編就為大家分享一篇beego獲取ajax數(shù)據(jù)的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • Go語言變量創(chuàng)建的五種方法

    Go語言變量創(chuàng)建的五種方法

    這篇文章主要介紹了Go語言五種變量創(chuàng)建的方法,本文給大家提到了匿名變量的優(yōu)點(diǎn),通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2020-02-02
  • 深入學(xué)習(xí)Golang的流程控制

    深入學(xué)習(xí)Golang的流程控制

    Go 語言是一門現(xiàn)代化的編程語言,以其簡潔高效、并發(fā)安全等特點(diǎn)被越來越多的開發(fā)者所使用。本文將深入探討 Go 語言中的流程控制,包括條件語句、循環(huán)語句以及控制語句等方面
    2023-05-05
  • Go語言實(shí)現(xiàn)支付寶支付與退款詳解

    Go語言實(shí)現(xiàn)支付寶支付與退款詳解

    本文詳細(xì)介紹使用Go語言對接支付寶支付與退款功能的步驟和注意事項(xiàng),包括PC端、WAP端和Android端支付實(shí)現(xiàn),以及退款功能的代碼實(shí)現(xiàn),介紹了GoPay庫的使用,幫助開發(fā)者快速集成支付寶支付到應(yīng)用中
    2024-10-10
  • Golang并發(fā)繞不開的重要組件之Channel詳解

    Golang并發(fā)繞不開的重要組件之Channel詳解

    Channel是一個(gè)提供可接收和發(fā)送特定類型值的用于并發(fā)函數(shù)通信的數(shù)據(jù)類型,也是Golang并發(fā)繞不開的重要組件之一,本文就來和大家深入聊聊Channel的相關(guān)知識(shí)吧
    2023-06-06
  • 使用dep 配置golang 開發(fā)環(huán)境的操作方法

    使用dep 配置golang 開發(fā)環(huán)境的操作方法

    下面小編就為大家?guī)硪黄褂胐ep 配置golang 開發(fā)環(huán)境的操作方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09

最新評論