參考:git
https://studygolang.com/pkgdoc程序員
http://c.biancheng.net/golang/concurrent/github
導入方式:golang
import "reflect"
reflect包實現了運行時反射,容許程序操做任意類型的對象。主要是實現了泛型,好比在一個函數中能根據傳入的參數來肯定參數的類型,而不是一開始就指定參數類型,這樣一個函數就能夠支持全部類型:編程
func Minimum(first interface{}, rest ...interface{}) interface{} { //... }
這樣就使得靜態的go有了不少動態的特性,reflect是配合interface{}來使用的c#
典型用法是用靜態類型interface{}保存一個值,經過調用TypeOf獲取其動態類型信息,該函數返回一個Type類型值。調用ValueOf函數返回一個Value類型值,該值表明運行時的數據。Zero接受一個Type類型參數並返回一個表明該類型零值的Value類型值。數組
反射是指在程序運行期對程序自己進行訪問和修改的能力。程序在編譯時,變量被轉換爲內存地址,變量名不會被編譯器寫入到可執行部分。在運行程序時,程序沒法獲取自身的信息。
支持反射的語言能夠在程序編譯期將變量的反射信息,如字段名稱、類型信息、結構體信息等整合到可執行文件中,並給程序提供接口訪問反射信息,這樣就能夠在程序運行期獲取類型的反射信息,而且有能力修改它們。緩存
在使用反射時,須要首先理解類型(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 (k Kind) String() string
定義k的字符串格式
2)類型(Type)
Go 程序中的類型(Type)指的是系統原生數據類型,如 int、string、bool、float32 等類型,以及使用 type 關鍵字定義的類型,這些類型的名稱就是其類型自己的名稱。例如使用 type A struct{} 定義結構體時,A 就是 struct{} 的類型。
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(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 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 string
StructTag是結構體字段的標籤。
通常來講,標籤字符串是(可選的)空格分隔的一連串`key1:"value1" key2:"value2"`對,該格式的要求比較嚴格,不能在key和value中隨意添加空格,不然會由於格式錯誤致使一系列問題。
⚠️每一個鍵都是不包含控制字符、空格、雙引號、冒號的非空字符串。每一個值都應被雙引號括起來,使用go字符串字面語法。
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(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(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(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(dir ChanDir, t Type) Type
ChanOf返回元素類型爲t、方向爲dir的通道類型。運行時GC強制將通道的元素類型的大小限定爲64kb。若是t的尺寸大於或等於該限制,本函數將會panic。
例如,若是 t 表示 int ,則 ChanOf(RecvDir,t) 表示 <-chan int
2.經過反射獲取值Value
反射不只能和上面同樣獲取值的類型(Type)信息,還能夠動態地獲取或設置變量的值
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(i interface{}) Value
ValueOf返回一個初始化爲i接口保管的具體值的Value,ValueOf(nil)返回Value零值。
一些經常使用方法:
func (v Value) Kind() Kind
Kind返回v持有的值的分類,若是v是Value零值,返回值爲Invalid
func (v Value) Type() Type
返回v持有的值的類型的Type表示。
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 (v Value) Interface() (i interface{})
本方法返回v當前持有的值(表示爲/保管在interface{}類型),等價於:
var i interface{} = (v's underlying value)
若是v是經過訪問非導出結構體字段獲取的,會致使panic。
func (v Value) Bool() bool
返回v持有的布爾值,若是v的Kind不是Bool會panic
func (v Value) Int() int64
返回v持有的有符號整數(表示爲int64),若是v的Kind不是Int、Int八、Int1六、Int3二、Int64會panic
func (v Value) Uint() uint64
返回v持有的無符號整數(表示爲uint64),如v的Kind不是Uint、Uintptr、Uint八、Uint1六、Uint3二、Uint64會panic
func (v Value) Float() float64
返回v持有的浮點數(表示爲float64),若是v的Kind不是Float3二、Float64會panic
func (v Value) Complex() complex128
返回v持有的複數(表示爲complex64),若是v的Kind不是Complex6四、Complex128會panic
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 (v Value) Bytes() []byte
返回v持有的[]byte類型值。若是v持有的值的類型不是[]byte會panic。
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 (v Value) NumField() int
返回v持有的結構體類型值的字段數,若是v的Kind不是Struct會panic
func (v Value) Field(i int) Value
返回結構體的第i個字段(的Value封裝)。若是v的Kind不是Struct或i出界會panic
上面兩個方法能夠結合用來循環獲取字段內容
func (v Value) FieldByIndex(index []int) Value
返回索引序列指定的嵌套字段的Value表示,等價於用索引中的值鏈式調用本方法,如v的Kind非Struct將會panic
func (v Value) FieldByName(name string) Value
返回該類型名爲name的字段(的Value封裝)(會查找匿名字段及其子字段),若是v的Kind不是Struct會panic;若是未找到會返回Value零值。
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 (v Value) IsNil() bool
IsNil報告v持有的值是否爲nil,經常使用於判斷指針是否爲空
v持有的值的分類必須是通道、函數、接口、映射、指針、切片之一;不然IsNil函數會致使panic。
注意IsNil並不老是等價於go語言中值與nil的常規比較。例如:若是v是經過使用某個值爲nil的接口調用ValueOf函數建立的,v.IsNil()返回真,可是若是v是Value零值,會panic。
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 (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝,相似於*操做,此時的Value表示的是Value的元素且能夠尋址。
若是v的Kind不是Interface或Ptr會panic;若是v持有的值爲nil,會返回Value零值。
func (v Value) Addr() Value
函數返回一個持有指向v持有者的指針的Value封裝,相似於&操做。
若是v.CanAddr()返回假,調用本方法會panic。Addr通常用於獲取結構體字段的指針或者切片的元素(的Value封裝)以便調用須要指針類型接收者的方法。
func (v Value) CanAddr() bool
返回是否能夠獲取v持有值的指針。能夠獲取指針的值被稱爲可尋址的。
若是一個值是切片或可尋址數組的元素、可尋址結構體的字段、或從指針解引用獲得的,該值即爲可尋址的。
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 (v Value) SetBool(x bool)
設置v的持有值。若是v的Kind不是Bool或者v.CanSet()返回假,會panic。
func (v Value) SetInt(x int64)
設置v的持有值。若是v的Kind不是Int、Int八、Int1六、Int3二、Int64之一或者v.CanSet()返回假,會panic。
func (v Value) SetUint(x uint64)
設置v的持有值。若是v的Kind不是Uint、Uintptr、Uint八、Uint1六、Uint3二、Uint64或者v.CanSet()返回假,會panic。
func (v Value) SetFloat(x float64)
設置v的持有值。若是v的Kind不是Float3二、Float64或者v.CanSet()返回假,會panic。
func (v Value) SetComplex(x complex128)
設置v的持有值。若是v的Kind不是Complex6四、Complex128或者v.CanSet()返回假,會panic。
func (v Value) SetBytes(x []byte)
設置v的持有值。若是v持有值不是[]byte類型或者v.CanSet()返回假,會panic。
func (v Value) SetString(x string)
設置v的持有值。若是v的Kind不是String或者v.CanSet()返回假,會panic。
func (v Value) SetPointer(x unsafe.Pointer)
設置v的持有值。若是v的Kind不是UnsafePointer或者v.CanSet()返回假,會panic。
func (v Value) SetCap(n int)
設定v持有值的容量。若是v的Kind不是Slice或者n出界(小於長度或超出容量),將致使panic
func (v Value) SetLen(n int)
設定v持有值的長度。若是v的Kind不是Slice或者n出界(小於零或超出容量),將致使panic
func (v Value) SetMapIndex(key, val Value)
用來給v的映射類型持有值添加/修改鍵值對,若是val是Value零值,則是刪除鍵值對。若是v的Kind不是Map,或者v的持有值是nil,將會panic。key的持有值必須能夠直接賦值給v持有值類型的鍵類型。val的持有值必須能夠直接賦值給v持有值類型的值類型。
func (v Value) Set(x Value)
將v的持有值修改成x的持有值。若是v.CanSet()返回假,會panic。x的持有值必須能直接賦給v持有值的類型。
3)經過反射調用函數
若是反射值對象(reflect.Value)中值的類型爲函數時,能夠經過 reflect.Value 調用該函數。
使用反射調用函數時,須要將參數使用反射值對象的切片 []reflect.Value 構造後傳入 Call() 方法中,調用完成時,函數的返回值經過 []reflect.Value 返回。
使用的方法:
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 (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 (v Value) NumMethod() int
返回v持有值的方法集的方法數目。
func (v Value) Method(i int) Value
返回v持有值類型的第i個方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不該包含接收者;返回值持有的函數老是使用v的持有者做爲接收者(即第一個參數)。若是i出界,或者v的持有值是接口類型的零值(nil),會panic。
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(typ Type) Value
New返回一個Value類型值,該值持有一個指向類型爲typ的新申請的零值的指針,返回值的Type爲PtrTo(typ)。
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(typ Type, len, cap int) Value
MakeSlice建立一個新申請的元素類型爲typ,長度len容量cap的切片類型的Value值。
func MakeMap(typ Type) Value
MakeMap建立一個特定映射類型的Value值。
func MakeChan(typ Type, buffer int) Value
MakeChan建立一個元素類型爲typ、有buffer個緩存的通道類型的Value值。
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 }