和Java語言同樣,Go也實現運行時反射,這爲咱們提供一種能夠在運行時操做任意類型對象的能力。好比咱們能夠查看一個接口變量的具體類型,看看一個結構體有多少字段,如何修改某個字段的值等。數組
在Go的反射定義中,任何接口都會由兩部分組成的,一個是接口的具體類型,一個是具體類型對應的值。好比var i int = 3 ,由於interface{}能夠表示任何類型,因此變量i能夠轉爲interface{},因此能夠把變量i當成一個接口,那麼這個變量在Go反射中的表示就是<Value,Type>,其中Value爲變量的值3,Type變量的爲類型int。ide
在Go反射中,標準庫爲咱們提供兩種類型來分別表示他們reflect.Value和reflect.Type,而且提供了兩個函數來獲取任意對象的Value和Type。函數
func main() { u:= User{"張三",20} t:=reflect.TypeOf(u) fmt.Println(t)
}
type User struct{ Name string Age int
}
reflect.TypeOf能夠獲取任意對象的具體類型,這裏經過打印輸出能夠看到是main.User這個結構體型。reflect.TypeOf函數接受一個空接口interface{}做爲參數,因此這個方法能夠接受任何類型的對象。spa
接着上面的例子,咱們看下如何反射獲取一個對象的Value。指針
v:=reflect.ValueOf(u) fmt.Println(v)
和TypeOf函數同樣,也能夠接受任意對象,能夠看到打印輸出爲{張三 20}。對於以上這兩種輸出,Go語言還經過fmt.Printf函數爲咱們提供了簡便的方法。code
fmt.Printf("%T\n",u) fmt.Printf("%v\n",u)
這個例子和以上的例子中的輸出同樣。orm
上面的例子咱們能夠經過reflect.ValueOf函數把任意類型的對象轉爲一個reflect.Value,那咱們若是咱們想逆向轉過回來呢,其實也是能夠的,reflect.Value爲咱們提供了Inteface方法來幫咱們作這個事情。繼續接上面的例子:對象
u1:=v.Interface().(User) fmt.Println(u1)
這樣咱們就又還原爲原來的User對象了,經過打印的輸出就能夠驗證。這裏能夠還原的緣由是由於在Go的反射中,把任意一個對象分爲reflect.Value和reflect.Type,而reflect.Value又同時持有一個對象的reflect.Value和reflect.Type,因此咱們能夠經過reflect.Value的Interface方法實現還原。如今咱們看看如何從一個reflect.Value獲取對應的reflect.Type。索引
t1:=v.Type() fmt.Println(t1)
如上例中,經過reflect.Value的Type方法就能夠得到對應的reflect.Type。接口
底層的類型是什麼意思呢?其實對應的主要是基礎類型,接口、結構體、指針這些,由於咱們能夠經過type關鍵字聲明不少新的類型,好比上面的例子,對象u的實際類型是User,可是對應的底層類型是struct這個結構體類型,咱們來驗證下。
fmt.Println(t.Kind())
經過Kind方法便可獲取,很是簡單,固然咱們也可使用Value對象的Kind方法,他們是等價的。
Go語言提供瞭如下這些最底層的類型,能夠看到,都是最基本的。
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
)
經過反射,咱們能夠獲取一個結構體類型的字段,也能夠獲取一個類型的導出方法,這樣咱們就能夠在運行時瞭解一個類型的結構,這是一個很是強大的功能。
for i:=0;i<t.NumField();i++ { fmt.Println(t.Field(i).Name) } for i:=0;i<t.NumMethod() ;i++ { fmt.Println(t.Method(i).Name) }
這個例子打印出結構體的全部字段名以及該結構體的方法。NumField方法獲取結構體有多少個字段,而後經過Field方法傳遞索引的方式,循環獲取每個字段,而後打印出他們的名字。
一樣的對於方法也相似,這裏再也不贅述。
假如咱們想在運行中動態的修改某個字段的值有什麼辦法呢?一種就是咱們常規的有提供的方法或者導出的字段能夠供咱們修改,還有一種是使用反射,這裏主要介紹反射。
func main() { x:=2 v:=reflect.ValueOf(&x) v.Elem().SetInt(100) fmt.Println(x)
}
以上就是經過反射修改一個變量的例子。
由於reflect.ValueOf函數返回的是一份值的拷貝,因此前提是咱們是傳入要修改變量的地址。
其次須要咱們調用Elem方法找到這個指針指向的值。
最後咱們就可使用SetInt方法修改值了。
以上有幾個重點,才能夠保證值能夠被修改,Value爲咱們提供了CanSet方法能夠幫助咱們判斷是否能夠修改該對象。
咱們如今能夠更新變量的值了,那麼如何修改結構體字段的值呢?你們本身試試。
結構體的方法咱們不光能夠正常的調用,還可使用反射進行調用。要想反射調用,咱們先要獲取到須要調用的方法,而後進行傳參調用,以下示例:
func main() { u:=User{"張三",20} v:=reflect.ValueOf(u) mPrint:=v.MethodByName("Print") args:=[]reflect.Value{reflect.ValueOf("前綴")} fmt.Println(mPrint.Call(args))
}
type User struct{ Name string Age int
}
func (u User) Print(prfix string){ fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age)
}
MethodByName方法可讓咱們根據一個方法名獲取一個方法對象,而後咱們構建好該方法須要的參數,最後調用Call就達到了動態調用方法的目的。
獲取到的方法咱們可使用IsValid 來判斷是否可用(存在)。
這裏的參數是一個Value類型的數組,因此須要的參數,咱們必需要經過ValueOf函數進行轉換。