您好,登錄后才能下訂單哦!
和Java語言一樣,Go也實現運行時反射,這為我們提供一種可以在運行時操作任意類型對象的能力。比如我們可以查看一個接口變量的具體類型,看看一個結構體有多少字段,如何修改某個字段的值等。
在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 string
Age 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.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函數進行轉換。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。