和Java語言一樣,Go也實現運行時反射,這為我們提供一種可以在運行時操作任意類型對象的能力。比如我們可以查看一個接口變量的具體類型,看看一個結構體有多少字段,如何修改某個字段的值等。
TypeOf和ValueOf
在Go的反射定義中,任何接口都會由兩部分組成的,一個是接口的具體類型,一個是具體類型對應的值。比如var i int = 3?,因為interface{}可以表示任何類型,所以變量i可以轉為interface{},所以可以把變量i當成一個接口,那么這個變量在Go反射中的表示就是<Value,Type>,其中Value為變量的值3,Type變量的為類型int。
在Go反射中,標準庫為我們提供兩種類型來分別表示他們reflect.Value和reflect.Type,并且提供了兩個函數來獲取任意對象的Value和Type。
func main() {u:= User{"張三",20}t:=reflect.TypeOf(u)fmt.Println(t)
}
type User struct{Name stringAge int
}
reflect.TypeOf可以獲取任意對象的具體類型,這里通過打印輸出可以看到是main.User這個結構體型。reflect.TypeOf函數接受一個空接口interface{}作為參數,所以這個方法可以接受任何類型的對象。
接著上面的例子,我們看下如何反射獲取一個對象的Value。
?v:=reflect.ValueOf(u)fmt.Println(v)
和TypeOf函數一樣,也可以接受任意對象,可以看到打印輸出為{張三 20}。對于以上這兩種輸出,Go語言還通過fmt.Printf函數為我們提供了簡便的方法。
????fmt.Printf("%T\n",u)fmt.Printf("%v\n",u)
這個例子和以上的例子中的輸出一樣。
reflect.Value轉原始類型
上面的例子我們可以通過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 ? ?
? ?BoolIntInt8Int16Int32Int64UintUint8Uint16Uint32Uint64UintptrFloat32Float64Complex64Complex128ArrayChanFuncInterfaceMapPtrSliceStringStructUnsafePointer
)
遍歷字段和方法
通過反射,我們可以獲取一個結構體類型的字段,也可以獲取一個類型的導出方法,這樣我們就可以在運行時了解一個類型的結構,這是一個非常強大的功能。
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:=2v:=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 stringAge 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函數進行轉換。
轉載于:https://blog.51cto.com/babyshen/2044187