Go語言之反射

和Java語言同樣,Go也實現運行時反射,這爲咱們提供一種能夠在運行時操做任意類型對象的能力。好比咱們能夠查看一個接口變量的具體類型,看看一個結構體有多少字段,如何修改某個字段的值等。數組


TypeOf和ValueOf


在Go的反射定義中,任何接口都會由兩部分組成的,一個是接口的具體類型,一個是具體類型對應的值。好比var i int = 3 ,由於interface{}能夠表示任何類型,因此變量i能夠轉爲interface{},因此能夠把變量i當成一個接口,那麼這個變量在Go反射中的表示就是<Value,Type>,其中Value爲變量的值3,Type變量的爲類型intide


在Go反射中,標準庫爲咱們提供兩種類型來分別表示他們reflect.Valuereflect.Type,而且提供了兩個函數來獲取任意對象的ValueType函數


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.Value轉原始類型


上面的例子咱們能夠經過reflect.ValueOf函數把任意類型的對象轉爲一個reflect.Value,那咱們若是咱們想逆向轉過回來呢,其實也是能夠的,reflect.Value爲咱們提供了Inteface方法來幫咱們作這個事情。繼續接上面的例子:對象


    u1:=v.Interface().(User)
    fmt.Println(u1)


這樣咱們就又還原爲原來的User對象了,經過打印的輸出就能夠驗證。這裏能夠還原的緣由是由於在Go的反射中,把任意一個對象分爲reflect.Valuereflect.Type,而reflect.Value又同時持有一個對象的reflect.Valuereflect.Type,因此咱們能夠經過reflect.ValueInterface方法實現還原。如今咱們看看如何從一個reflect.Value獲取對應的reflect.Type索引



  t1:=v.Type()
    fmt.Println(t1)


如上例中,經過reflect.ValueType方法就能夠得到對應的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函數進行轉換。

相關文章
相關標籤/搜索