參考:
https://studygolang.com/pkgdoc
http://c.biancheng.net/golang/concurrent/
導入方式:
import "reflect"
reflect包實現了運行時反射,允許程序操作任意類型的對象。主要是實現了泛型,比如在一個函數中能根據傳入的參數來確定參數的類型,而不是一開始就指定參數類型,這樣一個函數就可以支持所有類型:
func Minimum(first interface{}, rest ...interface{}) interface{} { //... }
這樣就使得靜態的go有了很多動態的特性,reflect是配合interface{}來使用的
典型用法是用靜態類型interface{}保存一個值,通過調用TypeOf獲取其動態類型信息,該函數返回一個Type類型值。調用ValueOf函數返回一個Value類型值,該值代表運行時的數據。Zero接受一個Type類型參數並返回一個代表該類型零值的Value類型值。
反射是指在程序運行期對程序本身進行訪問和修改的能力。程序在編譯時,變量被轉換為內存地址,變量名不會被編譯器寫入到可執行部分。在運行程序時,程序無法獲取自身的信息。
支持反射的語言可以在程序編譯期將變量的反射信息,如字段名稱、類型信息、結構體信息等整合到可執行文件中,並給程序提供接口訪問反射信息,這樣就可以在程序運行期獲取類型的反射信息,並且有能力修改它們。
1.類型(Type)與種類(Kind)
在使用反射時,需要首先理解類型(Type)和種類(Kind)的區別。編程中,使用最多的是類型,但在反射中,當需要區分一個大品種的類型時,就會用到種類(Kind)。
1)種類(Kind)指的是對象歸屬的品種,在 reflect 包中有如下定義:
type Kind uint
Kind代表Type類型值表示的具體分類。零值表示非法分類。
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 Ptr Slice String Struct UnsafePointer )
func (Kind) String
func (k Kind) String() string
定義k的字符串格式
2)類型(Type)
Go 程序中的類型(Type)指的是系統原生數據類型,如 int、string、bool、float32 等類型,以及使用 type 關鍵字定義的類型,這些類型的名稱就是其類型本身的名稱。例如使用 type A struct{} 定義結構體時,A 就是 struct{} 的類型。
type Type
type Type interface { // Kind返回該接口的具體分類 Kind() Kind // Name返回該類型在自身包內的類型名,如果是未命名類型會返回"" Name() string // PkgPath返回類型的包路徑,即明確指定包的import路徑,如"encoding/base64" // 如果類型為內建類型(string, error)或未命名類型(*T, struct{}, []int),會返回"" PkgPath() string // 返回類型的字符串表示。該字符串可能會使用短包名(如用base64代替"encoding/base64") // 也不保證每個類型的字符串表示不同。如果要比較兩個類型是否相等,請直接用Type類型比較。 String() string // 返回要保存一個該類型的值需要多少字節;類似unsafe.Sizeof Size() uintptr // 返回當從內存中申請一個該類型值時,會對齊的字節數 Align() int // 返回當該類型作為結構體的字段時,會對齊的字節數 FieldAlign() int // 如果該類型實現了u代表的接口,會返回真 Implements(u Type) bool // 如果該類型的值可以直接賦值給u代表的類型,返回真 AssignableTo(u Type) bool // 如該類型的值可以轉換為u代表的類型,返回真 ConvertibleTo(u Type) bool // 返回該類型的字位數。如果該類型的Kind不是Int、Uint、Float或Complex,會panic Bits() int // 返回array類型的長度,如非數組類型將panic Len() int // 返回該類型的元素類型,如果該類型的Kind不是Array、Chan、Map、Ptr或Slice,會panic Elem() Type // 返回map類型的鍵的類型。如非映射類型將panic Key() Type // 返回一個channel類型的方向,如非通道類型將會panic ChanDir() ChanDir
// 返回struct類型的字段數(匿名字段算作一個字段),如非結構體類型將panic NumField() int // 返回struct類型的第i個字段的類型,如非結構體或者i不在[0, NumField())內將會panic Field(i int) StructField // 返回索引序列指定的嵌套字段的類型, // 等價於用索引中每個值鏈式調用本方法,如非結構體將會panic FieldByIndex(index []int) StructField // 返回該類型名為name的字段(會查找匿名字段及其子字段), // 布爾值說明是否找到,如非結構體將panic FieldByName(name string) (StructField, bool) // 返回該類型第一個字段名滿足函數match的字段,布爾值說明是否找到,如非結構體將會panic FieldByNameFunc(match func(string) bool) (StructField, bool) // 如果函數類型的最后一個輸入參數是"..."形式的參數,IsVariadic返回真 // 如果這樣,t.In(t.NumIn() - 1)返回參數的隱式的實際類型(聲明類型的切片) // 如非函數類型將panic IsVariadic() bool // 返回func類型的參數個數,如果不是函數,將會panic NumIn() int // 返回func類型的第i個參數的類型,如非函數或者i不在[0, NumIn())內將會panic In(i int) Type // 返回func類型的返回值個數,如果不是函數,將會panic NumOut() int // 返回func類型的第i個返回值的類型,如非函數或者i不在[0, NumOut())內將會panic Out(i int) Type // 返回該類型的方法集中方法的數目 // 匿名字段的方法會被計算;主體類型的方法會屏蔽匿名字段的同名方法; // 匿名字段導致的歧義方法會濾除 NumMethod() int // 返回該類型方法集中的第i個方法,i不在[0, NumMethod())范圍內時,將導致panic // 對非接口類型T或*T,返回值的Type字段和Func字段描述方法的未綁定函數狀態 // 對接口類型,返回值的Type字段描述方法的簽名,Func字段為nil Method(int) Method // 根據方法名返回該類型方法集中的方法,使用一個布爾值說明是否發現該方法 // 對非接口類型T或*T,返回值的Type字段和Func字段描述方法的未綁定函數狀態 // 對接口類型,返回值的Type字段描述方法的簽名,Func字段為nil MethodByName(string) (Method, bool) // 內含隱藏或非導出方法 }
Type類型用來表示一個go類型。
不是所有go類型的Type值都能使用所有方法。請參見每個方法的文檔獲取使用限制。
在調用有分類限定的方法時,應先使用Kind()方法獲知類型的分類。
調用該分類不支持的方法會導致運行時的panic。
func TypeOf
func TypeOf(i interface{}) Type
TypeOf返回接口中保存的值的類型,TypeOf(nil)會返回nil。然后就能夠使用返回的Type值調用上面的方法獲得對象的內容
1》當反射對象的類型是原生數據類型時
舉例:
package main import( "fmt" "reflect" ) func main() { var a int typeA := reflect.TypeOf(a) fmt.Println(typeA) //int fmt.Println(typeA.Name()) //int,返回表示類型名稱的字符串 fmt.Println(typeA.Kind()) //int,返回 reflect.Kind 類型的常量 }
2》如果反射對象的類型是指針時
如果從指針中獲取反射對象時,這是不能直接使用Name()、Kind(),這樣得到的只是指針的信息(即name為空'',kind為'ptr')。如果想要得到的是該指針指向的變量的類型名稱河種類,就要使用Elem()
package main import( "fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ name string school string } //創建一個Student實例指針 stuPtr := &Student{"xiaoming", "peking university"} typeStuPtr := reflect.TypeOf(stuPtr) fmt.Println(typeStuPtr.Name()) //為空 fmt.Println(typeStuPtr.Kind()) //ptr //獲取該指針的元素 typeStu := typeStuPtr.Elem() fmt.Println(typeStu.Name()) //Student fmt.Println(typeStu.Kind()) //struct }
如果使用的不是指針那就能夠正常地使用Name() 、Kind()
package main import( "fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ name string school string } stu := Student{"xiaoming", "peking university"} typeStu := reflect.TypeOf(stu) fmt.Println(typeStu.Name()) //Student fmt.Println(typeStu.Kind()) //struct }
3)當反射對象的類型是結構體時
任意值通過 reflect.TypeOf() 獲得反射對象信息后,如果它的類型是結構體,可以通過反射值對象(reflect.Type)的 NumField() 和 Field() 方法獲得結構體成員的詳細信息。與成員獲取相關的 reflect.Type 的方法如下所示:
// 返回struct類型的字段數(匿名字段算作一個字段),如非結構體類型將panic
NumField() int // 返回struct類型的第i個字段的類型,如非結構體或者i不在[0, NumField())內將會panic Field(i int) StructField
//上面的兩個方法一般是配對使用,用來實現結構體成員餓遍歷操作
// 返回索引序列指定的嵌套字段的類型, // 等價於用索引中每個值鏈式調用本方法,如非結構體將會panic FieldByIndex(index []int) StructField // 返回該類型名為name的字段(會查找匿名字段及其子字段), // 布爾值說明是否找到,如非結構體將panic FieldByName(name string) (StructField, bool) // 返回該類型第一個字段名滿足函數match的字段,布爾值說明是否找到,如非結構體將會panic FieldByNameFunc(match func(string) bool) (StructField, bool)
結構體字段類型
type StructField
type StructField struct { // Name是字段的名字。PkgPath是非導出字段的包路徑,對導出字段該字段為""。 // 參見http://golang.org/ref/spec#Uniqueness_of_identifiers Name string PkgPath string //字段在結構體中的路徑 Type Type // 字段本身的反射類型對象,類型為 reflect.Type,可以進一步獲取字段的類型信息 Tag StructTag // 字段的標簽 Offset uintptr // 字段在結構體中的字節偏移量 Index []int // 用於Type.FieldByIndex時的索引切片 Anonymous bool // 是否匿名字段 }
StructField類型描述結構體中的一個字段的信息。
// 返回struct類型的第i個字段的類型,如非結構體或者i不在[0, NumField())內將會panic Field(i int) StructField
Field()等方法的返回值就是StructField,所以能夠使用Field(0).Tag來調用標簽
結構體字段中的標簽
type StructTag
type StructTag string
StructTag是結構體字段的標簽。
一般來說,標簽字符串是(可選的)空格分隔的一連串`key1:"value1" key2:"value2"`對,該格式的要求比較嚴格,不能在key和value中隨意添加空格,否則會因為格式錯誤導致一系列問題。
⚠️每個鍵都是不包含控制字符、空格、雙引號、冒號的非空字符串。每個值都應被雙引號括起來,使用go字符串字面語法。
func (StructTag) Get
func (tag StructTag) Get(key string) string
Get方法返回標簽字符串`key:"value"`對中鍵key對應的值。如果標簽中沒有該鍵,會返回""。如果標簽不符合標准格式,Get的返回值是不確定的。
舉例說明:
package main
import(
"fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ Name string School string `level:"national" id:"1"` //注明有兩個標簽level和id,用於給字段添加自定義信息,方便其他模塊根據信息進行不同功能的處理 //注意,注明標簽的時候不要隨便添加空格,否則會因為格式的問題在調用Get()函數時得不到結果 } stu := Student{"xiaoming", "peking university"} //獲取結構體實例的反射類型對象 typeStu := reflect.TypeOf(stu) fmt.Println(typeStu.NumField()) //2 //遍歷獲得結構體的所有字段 for i := 0; i < typeStu.NumField(); i++{ //獲取對應的字段類型 fieldType := typeStu.Field(i) fmt.Printf("fieldType: %v\n", fieldType) //循環兩次返回: //fieldType: {Name string 0 [0] false} //fieldType: {School string level:"national" id:"1" 16 [1] false} //打印獲取的字段名以及其注明的標簽信息 fmt.Printf("name : %v tag : %v\n", fieldType.Name, fieldType.Tag)//fieldType.Tag返回StructTag類型的值,然后可以調用Get()函數得到具體的tag值 //循環兩次返回: //name : Name tag : //name : School tag : level:"national" id:"1" } //通過字段名,找到字段的類型信息 if studentSchool, ok := typeStu.FieldByName("School"); ok { // fmt.Printf("studentSchool: %v\n", studentSchool)//studentSchool: {School string level:"national" id:"1" 16 [1] false} //使用Get()獲取標簽的value fmt.Println(studentSchool.Tag.Get("level"), studentSchool.Tag.Get("id"))//national 1 } }
4)Type中的其他函數
func PtrTo
func PtrTo(t Type) Type
PtrTo返回類型t的指針的類型。例如,如果 t 表示類型 Foo ,則 PtrT(t) 表示 * Foo
package main import( "fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ name string school string } //創建一個Student實例指針 stuPtr1 := &Student{"xiaoming", "peking university"} typeStuPtr1 := reflect.TypeOf(stuPtr1) fmt.Println(typeStuPtr1) //*main.Student fmt.Println(typeStuPtr1.Name()) //為空 fmt.Println(typeStuPtr1.Kind()) //ptr //獲取該指針的元素 typeStu1 := typeStuPtr1.Elem() fmt.Println(typeStu1.Name()) //Student fmt.Println(typeStu1.Kind()) //struct //上面直接傳入指針給TypeOf和下面使用PtrTo將其轉成指針類型的效果是一樣的 stu2 := Student{"zhangwei", "qinghua university"} typeStu2 := reflect.TypeOf(stu2) fmt.Println(typeStu2) //main.Student typeStuPtr2 := reflect.PtrTo(typeStu2) fmt.Println(typeStuPtr2) //*main.Student fmt.Println(typeStuPtr2.Name()) //為空 fmt.Println(typeStuPtr2.Kind()) //ptr //獲取該指針的元素 typeStu22 := typeStuPtr2.Elem() fmt.Println(typeStu22.Name()) //Student fmt.Println(typeStu22.Kind()) //struct }
func SliceOf
func SliceOf(t Type) Type
SliceOf返回類型t的切片的類型。例如,如果 t 表示 int , SliceOf(t) 表示 [] int
package main import( "fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ name string school string } stu := Student{"zhangwei", "qinghua university"} typeStu := reflect.TypeOf(stu) fmt.Println(typeStu) //main.Student typeStuSlice := reflect.SliceOf(typeStu) fmt.Println(typeStuSlice) //[]main.Student,讓其轉成一個切片類型 fmt.Println(typeStuSlice.Name()) //為空 fmt.Println(typeStuSlice.Kind()) //slice //獲取該指針的元素,當然切片中的元素的類型還是不變的 typeStuSliceElem := typeStuSlice.Elem() fmt.Println(typeStuSliceElem.Name()) //Student fmt.Println(typeStuSliceElem.Kind()) //struct }
func MapOf
func MapOf(key, elem Type) Type
MapOf返回一個鍵類型為key,值類型為elem的映射類型。如果key代表的類型不是合法的映射鍵類型(即它未實現go的==操作符),本函數會panic。
例如,如果 key 表示 int 並且 elem 表示 string,則 MapOf(k, e) 表示 map[int] string
package main import( "fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ name string school string } stu := Student{"zhangwei", "qinghua university"} key := 1 elemTypeStu := reflect.TypeOf(stu) fmt.Println(elemTypeStu) //main.Student keyTypeInt := reflect.TypeOf(key) fmt.Println(keyTypeInt) //int typeMap := reflect.MapOf(keyTypeInt, elemTypeStu) fmt.Println(typeMap) //map[int]main.Student,讓其轉成一個映射類型 fmt.Println(typeMap.Name()) //為空 fmt.Println(typeMap.Kind()) //map //獲取該映射的value的元素,當然value中的元素的類型還是不變的為Student typeMapElem := typeMap.Elem() fmt.Println(typeMapElem.Name()) //Student fmt.Println(typeMapElem.Kind()) //struct }
func ChanOf
func ChanOf(dir ChanDir, t Type) Type
ChanOf返回元素類型為t、方向為dir的通道類型。運行時GC強制將通道的元素類型的大小限定為64kb。如果t的尺寸大於或等於該限制,本函數將會panic。
例如,如果 t 表示 int ,則 ChanOf(RecvDir,t) 表示 <-chan int
2.通過反射獲取值Value
反射不僅能和上面一樣獲取值的類型(Type)信息,還可以動態地獲取或設置變量的值
type Value
type Value struct { // 存儲使用Value表示的值的類型 typ *rtype // 指針值數據,如果flagIndir設置,則指向數據 // 當flagIndir設置或typ.pointers()為true時有效 ptr unsafe.Pointer // 存儲值的元數據 flag }
Value為go值提供了反射接口。
不是所有go類型值的Value表示都能使用所有方法。請參見每個方法的文檔獲取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知該值的分類。調用該分類不支持的方法會導致運行時的panic。
Value類型的零值表示不持有某個值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回"<invalid Value>",所有其它方法都會panic。絕大多數函數和方法都永遠不返回Value零值。如果某個函數/方法返回了非法的Value,它的文檔必須顯式的說明具體情況。
如果某個go類型值可以安全的用於多線程並發操作,它的Value表示也可以安全的用於並發。
主要使用下面的方法獲取反射之對象(reflect.Value):
func ValueOf
func ValueOf(i interface{}) Value
ValueOf返回一個初始化為i接口保管的具體值的Value,ValueOf(nil)返回Value零值。
一些常用方法:
func (Value) Kind
func (v Value) Kind() Kind
Kind返回v持有的值的分類,如果v是Value零值,返回值為Invalid
func (Value) Type
func (v Value) Type() Type
返回v持有的值的類型的Type表示。
func (Value) Elem
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝。如果v的Kind不是Interface或Ptr會panic;如果v持有的值為nil,會返回Value零值。
舉例:
package main import( "fmt" "reflect" ) func main() { a := 1 //聲明並賦值得到一個int類型的a //獲取其的反射值對象 valueOfA := reflect.ValueOf(a) fmt.Println(valueOfA) //1 fmt.Println(valueOfA.Kind()) //int //獲取該值的類型,注意elem.(type)這種方法只能用在switch中 fmt.Println(reflect.TypeOf(valueOfA)) //reflect.Value //使用Value的Type()方法得到的是該對象的原始類型 fmt.Println(valueOfA.Type()) //int fmt.Println(valueOfA.Elem) //0x1091910 }
1)從反射值對象(reflect.Value)中獲取值的方法:
func (Value) Interface
func (v Value) Interface() (i interface{})
本方法返回v當前持有的值(表示為/保管在interface{}類型),等價於:
var i interface{} = (v's underlying value)
如果v是通過訪問非導出結構體字段獲取的,會導致panic。
func (Value) Bool
func (v Value) Bool() bool
返回v持有的布爾值,如果v的Kind不是Bool會panic
func (Value) Int
func (v Value) Int() int64
返回v持有的有符號整數(表示為int64),如果v的Kind不是Int、Int8、Int16、Int32、Int64會panic
func (Value) Uint
func (v Value) Uint() uint64
返回v持有的無符號整數(表示為uint64),如v的Kind不是Uint、Uintptr、Uint8、Uint16、Uint32、Uint64會panic
func (Value) Float
func (v Value) Float() float64
返回v持有的浮點數(表示為float64),如果v的Kind不是Float32、Float64會panic
func (Value) Complex
func (v Value) Complex() complex128
返回v持有的復數(表示為complex64),如果v的Kind不是Complex64、Complex128會panic
func (Value) Pointer
func (v Value) Pointer() uintptr
將v持有的值作為一個指針返回。本方法返回值不是unsafe.Pointer類型,以避免程序員不顯式導入unsafe包卻得到unsafe.Pointer類型表示的指針。如果v的Kind不是Chan、Func、Map、Ptr、Slice或UnsafePointer會panic。
如果v的Kind是Func,返回值是底層代碼的指針,但並不足以用於區分不同的函數;只能保證當且僅當v持有函數類型零值nil時,返回值為0。
如果v的Kind是Slice,返回值是指向切片第一個元素的指針。如果持有的切片為nil,返回值為0;如果持有的切片沒有元素但不是nil,返回值不會是0。
func (Value) Bytes
func (v Value) Bytes() []byte
返回v持有的[]byte類型值。如果v持有的值的類型不是[]byte會panic。
func (Value) String
func (v Value) String() string
返回v持有的值的字符串表示。因為go的String方法的慣例,Value的String方法比較特別。和其他獲取v持有值的方法不同:v的Kind是String時,返回該字符串;v的Kind不是String時也不會panic而是返回格式為"<T value>"的字符串,其中T是v持有值的類型。
舉例:
package main import( "fmt" "reflect" ) func main() { a := 1 //聲明並賦值得到一個int類型的a //獲取其的反射值對象 valueOfA := reflect.ValueOf(a) fmt.Println(valueOfA) //1 //獲取該值的類型,注意elem.(type)這種方法只能用在switch中 fmt.Println(reflect.TypeOf(valueOfA)) //reflect.Value //調用interface()方法獲取interface{}類型的值,然后使用類型斷言進行轉換成int類型 changedA1 := valueOfA.Interface().(int) fmt.Println(changedA1) //1 fmt.Println(reflect.TypeOf(changedA1)) //int //還有另一種類似的方法,就是調用Int()方法將其先轉換成int64類型,然后再轉成int類型 changedA2 := int(valueOfA.Int()) fmt.Println(changedA2) //1 fmt.Println(reflect.TypeOf(changedA2))//int }
當然,其他的方法都是類似的,這幾個函數的作用就是將Value類型的值又轉回其原生數據類型
2)如果反射值對象的類型是結構體,其可以使用下面的幾種方法:
func (Value) NumField
func (v Value) NumField() int
返回v持有的結構體類型值的字段數,如果v的Kind不是Struct會panic
func (Value) Field
func (v Value) Field(i int) Value
返回結構體的第i個字段(的Value封裝)。如果v的Kind不是Struct或i出界會panic
上面兩個方法可以結合用來循環獲取字段內容
func (Value) FieldByIndex
func (v Value) FieldByIndex(index []int) Value
返回索引序列指定的嵌套字段的Value表示,等價於用索引中的值鏈式調用本方法,如v的Kind非Struct將會panic
func (Value) FieldByName
func (v Value) FieldByName(name string) Value
返回該類型名為name的字段(的Value封裝)(會查找匿名字段及其子字段),如果v的Kind不是Struct會panic;如果未找到會返回Value零值。
func (Value) FieldByNameFunc
func (v Value) FieldByNameFunc(match func(string) bool) Value
返回該類型第一個字段名滿足match的字段(的Value封裝)(會查找匿名字段及其子字段),如果v的Kind不是Struct會panic;如果未找到會返回Value零值。
舉例:
package main import( "fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ name string school string } stu := Student{"xiaoming", "peking university"} valueStu := reflect.ValueOf(stu) //使用NumField()得到結構體中字段的數量,然后迭代得到字段的值Field(i)和類型Field(i).Type() for i := 0; i < valueStu.NumField(); i++{ fmt.Printf("fieldValue: %v, FieldType: %v\n", valueStu.Field(i), valueStu.Field(i).Type()) //迭代返回: // fieldValue: xiaoming, FieldType: string // fieldValue: peking university, FieldType: string } fmt.Println() //根據名字查找字段 fmt.Printf("fieldValue: %v, FieldType: %v\n", valueStu.FieldByName("name"), valueStu.FieldByName("name").Type()) //fieldValue: xiaoming, FieldType: string //根據索引值查找字段,[]int{1}的意思就是讀取索引為1的字段,如果該字段也是個結構體,如果想獲得該字段的索引為2的值,應寫成[]int{1,2} fmt.Printf("fieldValue: %v, FieldType: %v\n", valueStu.FieldByIndex([]int{1}), valueStu.FieldByIndex([]int{1}).Type()) //fieldValue: peking university, FieldType: string }
3)判斷反射值的空和有效性
func (Value) IsNil
func (v Value) IsNil() bool
IsNil報告v持有的值是否為nil,常用於判斷指針是否為空
v持有的值的分類必須是通道、函數、接口、映射、指針、切片之一;否則IsNil函數會導致panic。
注意IsNil並不總是等價於go語言中值與nil的常規比較。例如:如果v是通過使用某個值為nil的接口調用ValueOf函數創建的,v.IsNil()返回真,但是如果v是Value零值,會panic。
func (Value) IsValid
func (v Value) IsValid() bool
IsValid返回v是否持有一個值,常用於判斷返回值是否有效
如果v是Value零值會返回false,此時v除了IsValid、String、Kind之外的方法都會導致panic。絕大多數函數和方法都永遠不返回Value零值。
如果某個函數/方法返回了非法的Value,它的文檔必須顯式的說明具體情況。
舉例:
package main import( "fmt" "reflect" ) func main() { //nil值 // fmt.Printf(" nil isNil :%v\n", reflect.ValueOf(nil).IsNil())//panic: reflect: call of reflect.Value.IsNil on zero Value fmt.Printf(" nil isValid :%v\n", reflect.ValueOf(nil).IsValid()) //空指針 var a *int fmt.Printf(" a *int isNil :%v\n", reflect.ValueOf(a).IsNil()) fmt.Printf(" a *int isValid :%v\n", reflect.ValueOf(a).IsValid()) //空映射 m := map[int]int{} fmt.Printf(" m map[int]int{} isNil :%v\n", reflect.ValueOf(m).IsNil()) fmt.Printf(" m map[int]int{} isValid :%v\n", reflect.ValueOf(m).IsValid()) //獲取其中不存在的鍵 fmt.Printf(" m map[3]'s value' isValid :%v\n", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid()) //空結構體 s := struct{}{} // fmt.Printf(" s struct{}{} isNil :%v\n", reflect.ValueOf(s).IsNil())//panic: reflect: call of reflect.Value.IsNil on struct Value fmt.Printf(" s struct{}{} isValid :%v\n", reflect.ValueOf(s).IsValid()) //獲取一個不存在的字段 fmt.Printf(" s.name isValid :%v\n", reflect.ValueOf(s).FieldByName("name").IsValid()) //獲取一個不存在的方法 fmt.Printf(" s method isValid :%v\n", reflect.ValueOf(s).MethodByName("Method").IsValid()) }
返回:
userdeMBP:go-learning user$ go run test.go nil isValid :false a *int isNil :true a *int isValid :true m map[int]int{} isNil :false m map[int]int{} isValid :true m map[3]'s value' isValid :false s struct{}{} isValid :true s.name isValid :false s method isValid :false
4)通過反射修改變量的值
1》獲取反射值元素即判斷其是否能夠進行修改的方法:
func (Value) Elem
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝,類似於*操作,此時的Value表示的是Value的元素且可以尋址。
如果v的Kind不是Interface或Ptr會panic;如果v持有的值為nil,會返回Value零值。
func (Value) Addr
func (v Value) Addr() Value
函數返回一個持有指向v持有者的指針的Value封裝,類似於&操作。
如果v.CanAddr()返回假,調用本方法會panic。Addr一般用於獲取結構體字段的指針或者切片的元素(的Value封裝)以便調用需要指針類型接收者的方法。
func (Value) CanAddr
func (v Value) CanAddr() bool
返回是否可以獲取v持有值的指針。可以獲取指針的值被稱為可尋址的。
如果一個值是切片或可尋址數組的元素、可尋址結構體的字段、或從指針解引用得到的,該值即為可尋址的。
func (Value) CanSet
func (v Value) CanSet() bool
如果v持有的值可以被修改,CanSet就會返回真。
⚠️一個是否能夠被修改的值必須滿足下面的兩個條件
只有一個Value持有值 1>可以被尋址 2>同時又不是來自非導出字段時,它才可以被修改。
如果CanSet返回假,調用Set或任何限定類型的設置函數(如SetBool、SetInt64)都會panic。
1>可被尋址
package main import( "fmt" "reflect" ) func main() { a := 1 ValueA := reflect.ValueOf(a) fmt.Println(ValueA.CanSet()) //false fmt.Println(ValueA.CanAddr()) //false ValueAPtr := reflect.ValueOf(&a) ValueA2 := ValueAPtr.Elem() //此時的ValueA2表示的是a的元素且可以尋址 fmt.Println(ValueA2.CanSet()) //true fmt.Println(ValueA2.CanAddr()) //true ValueA2.SetInt(7) //修改值為7 fmt.Println(ValueA2.Int()) //7 }
2>被導出
比如在結構體中,如果你的字段命名是第一個字符為小寫則說明該字段是不被導出的
package main import( "fmt" "reflect" ) func main() { //創建一個結構體Student type Student struct{ name string //小寫,不被導出 School string //大寫,被導出 } stu := Student{"xiaoming", "peking university"} valueStuPtr := reflect.ValueOf(&stu) valueStu := valueStuPtr.Elem() //可尋址 fmt.Println(valueStu.CanSet()) //true fmt.Println(valueStu.CanAddr()) //true stuName := valueStu.FieldByName("name") fmt.Println(stuName.CanAddr()) //true,可尋址 fmt.Println(stuName.CanSet()) //false,但是因為不被導出,所以不可以修改 stuSchool:= valueStu.FieldByName("School") fmt.Println(stuSchool.CanSet()) //true fmt.Println(stuSchool.CanAddr()) //true stuSchool.SetString("qinghua university") fmt.Println(stuSchool.String())//qinghua university }
2)值修改時調用的方法:
func (Value) SetBool
func (v Value) SetBool(x bool)
設置v的持有值。如果v的Kind不是Bool或者v.CanSet()返回假,會panic。
func (Value) SetInt
func (v Value) SetInt(x int64)
設置v的持有值。如果v的Kind不是Int、Int8、Int16、Int32、Int64之一或者v.CanSet()返回假,會panic。
func (Value) SetUint
func (v Value) SetUint(x uint64)
設置v的持有值。如果v的Kind不是Uint、Uintptr、Uint8、Uint16、Uint32、Uint64或者v.CanSet()返回假,會panic。
func (Value) SetFloat
func (v Value) SetFloat(x float64)
設置v的持有值。如果v的Kind不是Float32、Float64或者v.CanSet()返回假,會panic。
func (Value) SetComplex
func (v Value) SetComplex(x complex128)
設置v的持有值。如果v的Kind不是Complex64、Complex128或者v.CanSet()返回假,會panic。
func (Value) SetBytes
func (v Value) SetBytes(x []byte)
設置v的持有值。如果v持有值不是[]byte類型或者v.CanSet()返回假,會panic。
func (Value) SetString
func (v Value) SetString(x string)
設置v的持有值。如果v的Kind不是String或者v.CanSet()返回假,會panic。
func (Value) SetPointer
func (v Value) SetPointer(x unsafe.Pointer)
設置v的持有值。如果v的Kind不是UnsafePointer或者v.CanSet()返回假,會panic。
func (Value) SetCap
func (v Value) SetCap(n int)
設定v持有值的容量。如果v的Kind不是Slice或者n出界(小於長度或超出容量),將導致panic
func (Value) SetLen
func (v Value) SetLen(n int)
設定v持有值的長度。如果v的Kind不是Slice或者n出界(小於零或超出容量),將導致panic
func (Value) SetMapIndex
func (v Value) SetMapIndex(key, val Value)
用來給v的映射類型持有值添加/修改鍵值對,如果val是Value零值,則是刪除鍵值對。如果v的Kind不是Map,或者v的持有值是nil,將會panic。key的持有值必須可以直接賦值給v持有值類型的鍵類型。val的持有值必須可以直接賦值給v持有值類型的值類型。
func (Value) Set
func (v Value) Set(x Value)
將v的持有值修改為x的持有值。如果v.CanSet()返回假,會panic。x的持有值必須能直接賦給v持有值的類型。
3)通過反射調用函數
如果反射值對象(reflect.Value)中值的類型為函數時,可以通過 reflect.Value 調用該函數。
使用反射調用函數時,需要將參數使用反射值對象的切片 []reflect.Value 構造后傳入 Call() 方法中,調用完成時,函數的返回值通過 []reflect.Value 返回。
使用的方法:
func (Value) Call
func (v Value) Call(in []Value) []Value
Call方法使用輸入的參數in調用v持有的函數。例如,如果len(in) == 3,v.Call(in)代表調用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func會panic。它返回函數所有輸出結果的Value封裝的切片。和go代碼一樣,每一個輸入實參的持有值都必須可以直接賦值給函數對應輸入參數的類型。如果v持有值是可變參數函數,Call方法會自行創建一個代表可變參數的切片,將對應可變參數的值都拷貝到里面。
func (Value) CallSlice
func (v Value) CallSlice(in []Value) []Value
CallSlice調用v持有的可變參數函數,會將切片類型的in[len(in)-1](的成員)分配給v的最后的可變參數。例如,如果len(in) == 3,v.Call(in)代表調用v(in[0], in[1], in[2])(其中Value值表示其持有值,可變參數函數的可變參數位置提供一個切片並跟三個點號代表"解切片")。如果v的Kind不是Func或者v的持有值不是可變參數函數,會panic。它返回函數所有輸出結果的Value封裝的切片。和go代碼一樣,每一個輸入實參的持有值都必須可以直接賦值給函數對應輸入參數的類型。
舉例:
package main import( "fmt" "reflect" ) func add(a, b int) int { return a + b } func main() { // 將函數add包裝為反射值對象 funcValue := reflect.ValueOf(add) // 構造函數add的參數, 傳入兩個整型值 paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} // 反射調用函數Call() retList := funcValue.Call(paramList) // 獲取第一個返回值, 取整數值 fmt.Println(retList[0].Int()) //返回 30 }
⚠️
反射調用函數的過程需要構造大量的 reflect.Value 和中間變量,對函數參數值進行逐一檢查,還需要將調用參數復制到調用函數的參數內存中。調用完畢后,還需要將返回值轉換為 reflect.Value,用戶還需要從中取出調用值。因此,反射調用函數的性能問題尤為突出,不建議大量使用反射函數調用。
其他相關函數有:
func (Value) NumMethod
func (v Value) NumMethod() int
返回v持有值的方法集的方法數目。
func (Value) Method
func (v Value) Method(i int) Value
返回v持有值類型的第i個方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果i出界,或者v的持有值是接口類型的零值(nil),會panic。
func (Value) MethodByName
func (v Value) MethodByName(name string) Value
返回v的名為name的方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果未找到該方法,會返回一個Value零值。
舉例:
package main import( "fmt" "reflect" ) type S struct{} func (s *S) Add(a, b int) int { return a + b } func main() { s := &S{} funcValue := reflect.ValueOf(s) // 構造函數add的參數, 傳入兩個整型值 paramList1 := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} paramList2 := []reflect.Value{reflect.ValueOf(20), reflect.ValueOf(40)} fmt.Println(funcValue.NumMethod()) //1 //獲得方法並反射調用函數Call() retList1 := funcValue.Method(0).Call(paramList1) fmt.Println(retList1[0].Int()) //30 retList2 := funcValue.MethodByName("Add").Call(paramList2) fmt.Println(retList2[0].Int()) //60 }
3.通過Type創建Value實例
1)new方法:
func New
func New(typ Type) Value
New返回一個Value類型值,該值持有一個指向類型為typ的新申請的零值的指針,返回值的Type為PtrTo(typ)。
func NewAt
func NewAt(typ Type, p unsafe.Pointer) Value
NewAt返回一個Value類型值,該值持有一個指向類型為typ、地址為p的值的指針。
舉例:
package main import( "fmt" "reflect" ) func main() { a := 1 typeA := reflect.TypeOf(a) newA := reflect.New(typeA) //類似於New(int),返回的是*int類型 fmt.Println(newA.Kind()) //ptr fmt.Println(newA.Type()) //*int //獲取該指針元素 elemNewA := newA.Elem() fmt.Println(elemNewA.Kind()) //int fmt.Println(elemNewA.Type()) //int }
2)make方法—只支持切片,函數,映射和通道:
func MakeSlice
func MakeSlice(typ Type, len, cap int) Value
MakeSlice創建一個新申請的元素類型為typ,長度len容量cap的切片類型的Value值。
func MakeMap
func MakeMap(typ Type) Value
MakeMap創建一個特定映射類型的Value值。
func MakeChan
func MakeChan(typ Type, buffer int) Value
MakeChan創建一個元素類型為typ、有buffer個緩存的通道類型的Value值。
func MakeFunc
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
MakeFunc返回一個具有給定類型、包裝函數fn的函數的Value封裝。當被調用時,該函數會
- 將提供給它的參數轉化為Value切片 - 執行results := fn(args) - 將results中每一個result依次排列作為返回值
函數fn的實現可以假設參數Value切片匹配typ類型指定的參數數目和類型。如果typ表示一個可變參數函數類型,參數切片中最后一個Value本身必須是一個包含所有可變參數的切片。fn返回的結果Value切片也必須匹配typ類型指定的結果數目和類型。
Value.Call方法允許程序員使用Value調用一個有類型約束的函數;反過來,MakeFunc方法允許程序員使用Value實現一個有類型約束的函數。
下例是一個用MakeFunc創建一個生成不同參數類型的swap函數的代碼及其說明。
package main import( "fmt" "reflect" ) func main() { swap := func(in []reflect.Value) []reflect.Value { return []reflect.Value{in[1], in[0]} } makeSwap := func(fptr interface{}) { //得到傳入指針的元素 fn := reflect.ValueOf(fptr).Elem() //將傳給fptr指針表示的函數的參數args轉化成[]reflect.Value類型作為swap函數的in參數傳入 //然后運行swap([]reflect.Value{args}),然后得到[]Value類型的結果results //然后將results中的值依次排列作為值v傳出 v := reflect.MakeFunc(fn.Type(), swap)//等待參數傳入 fn.Set(v) } var intSwap func(int, int) (int, int) makeSwap(&intSwap) //這個就是將參數0,1傳入,運行swap fmt.Println(intSwap(0, 1)) //返回:1 0 var floatSwap func(float64, float64) (float64, float64) makeSwap(&floatSwap) fmt.Println(floatSwap(2.72, 3.14)) //返回:3.14 2.72 }