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


TypeOf和ValueOf


在Go的反射定義中,任何接口都會由兩部分組成的,一個是接口的具體類型,一個是具體類型對應的值。比如var i int = 3?,因為interface{}可以表示任何類型,所以變量i可以轉為interface{},所以可以把變量i當成一個接口,那么這個變量在Go反射中的表示就是<Value,Type>,其中Value為變量的值3,Type變量的為類型int


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


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.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 ? ?
? ?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函數進行轉換。