Let's GoLang(一):反射
學了好長一段時間Go了, 最近準備寫一系列文章來總結學習的經驗,第一篇是反射。
最開始接觸到反射是在Java中,當時的理解是:反射可以在執行時來載入編譯好的位元組碼,而不需要進行編譯的過程,非常的靈活。在Go中也是如此:如果我們想要使用在編寫程式時不存在的資訊,例如變數;或者將檔案或網路請求中的資料對映到變數中,在這些情況下,就需要使用反射。 Reflection使我們能夠在執行時檢查型別,檢查、修改和建立變數,函式和結構等,非常強大。
在Go中的反射圍繞三個概念構建:Type,Kind和Value,在Go中主要使用標準庫中的reflect包來實現呼叫反射的各種功能。
基本用法
首先讓我們來看看reflect.Type。可以使用反射來獲取變數var的型別,函式呼叫varType := reflect.TypeOf(var), 這將返回一個reflect.Type型別的變數,該變數包含有關定義傳入變數的型別的各種資訊的方法。
第一個方法是Name(),它會返回型別的名稱,某些型別(如slice或pointer)沒有名稱,此方法返回空字串。
下一個方法是Kind(),非常有用。kind是由 slice,map,pointer, struct,inerface,string,array,func,int或其他一些基本型別構成。Kind() 和Name() 之間的差異可能很難理解,但可以理解為:如果定義一個名為Foo的struct,則kind() 返回struct,Name() 返回Foo。
舉個簡單的例子吧:
type User struct { Name string Age int } user := User{ "jack", 20, } userType := reflect.TypeOf(user) fmt.Println(userType.Name()) fmt.Println(userType.Kind())
可以看到列印的結果分別是:User, struct。
使用反射時需要注意的一件事:反射包中的所有內容都假定使用者知道自己在做什麼,如果使用不當,許多函式和方法呼叫都會造成panic。例如,如果在reflect.Type上呼叫一個方法,該方法與當前型別不同的型別相關聯,那麼程式將會崩潰。一種不錯的解決方案是,始終記得使用Kind() 來判斷反射值的型別
如果變數是pointer,map,slice,channel或array,則可以使用 varType.Elem() 找到包含的型別。
如果變數是struct,則可以使用反射來獲取結構中的欄位數,並獲取reflect.StructField結構中包含的每個欄位的結構。 reflect.StructField中包含欄位上的name,order,type和tag等資訊。
還是看上面那個例子:
type User struct { Name string `json:"name"` Age int `json:"age"` } user := User{ "jack", 20, } userType := reflect.TypeOf(user) firstField := userType.Field(0) fmt.Println(firstField.Name, firstField.Type, firstField.Tag)
結果是:Namestringjson:"name"
使用反射建立新的例項
除了用來獲取變數的型別之外,還可以使用反射來讀取,設定或建立值。首先,使用refVal := reflect.ValueOf(val) 為變數建立一個reflect.Value例項。如果要使用反射來修改值,則必須獲取指向變數的指標 refPtrVal := reflect.ValueOf(&val),如果不這樣做,則只能使用反射讀取值,但不能修改它。
舉個簡單的例子:
type User struct { Name string `json:"name"` Age int `json:"age"` } user := User{ "jack", 20, } userValue := reflect.ValueOf(&user).Elem() userValue.FieldByName("Name").SetString("rose") fmt.Println(user)
列印的結果是:{rose 20}
如果要建立新值,可以使用函式呼叫 newPtrVal:= reflect.New(varType),傳入reflect.Type。這將返回一個可以修改的指標值。使用Elem().Set()如上所述。就像下面這樣:
type User struct { Name string `json:"name"` Age int `json:"age"` } user := User{ "jack", 20, } userType := reflect.TypeOf(user) newUser := reflect.New(userType) newUser.Elem().FieldByName("Name").SetString("rose") newUser.Elem().FieldByName("Age").SetInt(10) fmt.Println(user, newUser)
最後,可以通過呼叫Interface()方法返回到正常變數。因為Go沒有泛型,所以變數的原始型別丟失了, 所以我們需要將空介面轉換為實際型別才能使用它:
user := User{ "jack", 20, } userInterface := reflect.ValueOf(user).Interface() originalUser, isOk := userInterface.(User) if isOk { fmt.Println(originalUser.Name, originalUser.Age) }
使用反射建立需要make才能建立型別例項
除了建立內建和使用者定義型別的例項之外,還可以使用反射來建立通常需要make函式的例項。可以使用reflect.MakeSlice,reflect.MakeMap和reflect.MakeChan函式製作slice,map或channel。在所有情況下,首先需要呼叫建立一個reflect.Type,然後呼叫上面提到的這些方法來獲取一個可以使用反射操作的reflect.Value。例如:
intSlice := make([]int, 0) initMap := make(map[string]int) sliceType := reflect.TypeOf(intSlice) mapType := reflect.TypeOf(initMap) reflectSlice := reflect.MakeSlice(sliceType, 0, 0) reflectMap := reflect.MakeMap(mapType) v := 10 rv := reflect.ValueOf(v) reflectSlice = reflect.Append(reflectSlice, rv) intSlice2 := reflectSlice.Interface().([]int) fmt.Println(intSlice2) k := "hello" rk := reflect.ValueOf(k) reflectMap.SetMapIndex(rk, rv) mapStringInt2 := reflectMap.Interface().(map[string]int) fmt.Println(mapStringInt2)
使用make建立函式
我們不僅可以使用反射建立儲存資料的變數,還可以反使用reflect.MakeFunc函式建立新函式。舉個栗子,如果我們想要計算一個函式執行的時長,可以像下面這樣:
func testMakeFunc(count int){ sum := 0 for i := 0; i< count; i++ { sum += 1 } fmt.Println(sum) } func main() { funcType := reflect.TypeOf(testMakeFunc) funcValue := reflect.ValueOf(testMakeFunc) newFunc := reflect.MakeFunc(funcType, func(args []reflect.Value) (results []reflect.Value) { start := time.Now() out := funcValue.Call(args) end := time.Now() fmt.Println(end.Sub(start)) return out }) var count int = 1e8 newFunc.Call([]reflect.Value{reflect.ValueOf(count)}) }
不僅如此,我們還能借助reflect在執行時,根據某些資料來動態的生成struct,非常強大:
func testMakeStruct(args ...interface{}) interface{} { var structList []reflect.StructField for index, value := range args { argType := reflect.TypeOf(value) item := reflect.StructField{ Name: fmt.Sprintf("Item%d", index), Type: argType, } structList = append(structList, item) } structType := reflect.StructOf(structList) structValue := reflect.New(structType) return structValue.Interface() } func main() { structValue := reflect.ValueOf(testMakeStruct(1, true, "hello world")) fmt.Println(structValue) }
反射不能做什麼?
前面我們說到了反射的各種功能,但是反射不是萬能的。例如:反射不能建立方法,因此你無法在執行時實現介面。
反射的作用
大多數情況下,如果使用interface {}型別的引數呼叫函式,則很可能會使用反射來檢查或更改引數的值。
反射的最常見用途是從檔案或網路中序列化和反序列化資料。可以將JSON或資料庫對映成指定資料結構,比如struct,slice,map等, 也可以反過來。讓我們看一下如何通過檢視實現JSON解組的Go標準庫中的程式碼來完成這項工作。
為了將JSON字串中的值對映到變數中,我們呼叫json.Unmarshal函式。它包含兩個引數:
- 第一個引數是JSON,型別是[]byte
- 第二個引數是變數,即反序列化JSON生成的變數
如果你看過unmarshal的原始碼,它是這樣的:
func (d *decodeState) unmarshal(v interface{}) (err error) { <skip over some setup> rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr || rv.IsNil() { return &InvalidUnmarshalError{reflect.TypeOf(v)} } d.scan.reset() // We decode rv not rv.Elem because the Unmarshaler interface // test must be applied at the top level of the value. d.value(rv) return d.savedError }
我們可以看到,其中使用反射來驗證v是否為正確的變數型別,也就是指標。如果是,則將v的反射版本(稱為rv)傳遞給value方法。在通過一些函式和方法處理之後,使用反射以不同的方式得到rv,具體取決於JSON是表示陣列,物件還是文字。例如,在解析JSON物件時,標準庫以多種方式使用反射。 我們可以看到,在處理不同型別時,會執行不同的操作:
switch v.Kind() { case reflect.Map: // Map key must either have string kind, have an integer kind, // or be an encoding.TextUnmarshaler. t := v.Type() switch t.Key().Kind() { case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: default: if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) d.off -- d.next() // skip over { } in input return } } if v.IsNil() { v.Set(reflect.MakeMap(t)) } case reflect.Struct: // ok default: d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) d.off -- d.next() // skip over { } in input return }
subv = v destring = f.quoted for _, i := range f.index { if subv.Kind() == reflect.Ptr { if subv.IsNil() { subv.Set(reflect.New(subv.Type().Elem())) } subv = subv.Elem() } subv = subv.Field(i) }
上面分別是對map和struct的處理,更多的處理可以看decode的原始碼。