先重復一遍反射三定律:
1.反射可以將“接口類型變量”轉換為“反射類型對象”。
2.反射可以將“反射類型對象”轉換為“接口類型變量”。
3.如果要修改“反射類型對象”,其值必須是“可寫的”(settable)
總結
下面詳細說明了Golang的反射reflect的各種功能和用法,都附帶有相應的示例,相信能夠在工程應用中進行相應實踐,總結一下就是:
-
反射可以大大提高程序的靈活性,使得interface{}有更大的發揮余地
- 反射必須結合interface才玩得轉
- 變量的type要是concrete type的(也就是interface變量)才有反射一說
-
反射可以將“接口類型變量”轉換為“反射類型對象”
- 反射使用 TypeOf 和 ValueOf 函數從接口中獲取目標對象信息
-
反射可以將“反射類型對象”轉換為“接口類型變量
- reflect.value.Interface().(已知的類型)
- 遍歷reflect.Type的Field獲取其Field
-
反射可以修改反射類型對象,但是其值必須是“addressable”
- 想要利用反射修改對象狀態,前提是 interface.data 是 settable,即 pointer-interface
-
通過反射可以“動態”調用方法
-
因為Golang本身不支持模板,因此在以往需要使用模板的場景下往往就需要使用反射(reflect)來實現
package main import ( "fmt" "io" "math" "os" "reflect" ) func main() { // 反射的機制就是在運行時動態的調用對象的方法和屬性 // 官方自帶的reflect就是反射相關的, golang中的grpc就是通過反射實現的 // 變量包括(type,value)兩部分 // type又分為:static type,concrete type //簡單來說static type是編碼時看見的類型,例如:int string // concrete type 是runtime系統看見的類型 // 類型斷言是否能成功取決於concrete type,而不是static type // 因此一個reader變量如果他的 concrete type 也實現了write方法的話,它也可以被類型斷言為writer // 反射主要與golang的interface類型相關(它的type是concrete type),只有interface類型才有反射一說 // 在go中每個interface變量都有一個對應的pair,pair中記錄了實際變量的值和類型(value type) // value記錄了實際變量的值,type記錄了實際變量的類型 // 一個interface類型的變量包含了兩個指針,一個指針指向了值得類型(concrete type),一個指針指向了實際的值(value) // 1. interface和反射 // tty pair<type:*os.File, value:&os.File{}> tty, _ := os.OpenFile("./test.txt", os.O_RDWR, 0) var r io.Reader // r pair<type:*os.File, value:&os.File{}> // 這個pair在接口變量的連續賦值過程中是不變的 r = tty var w io.Writer // w pair<type:*os.File, value:&os.File{}> // 將這個接口變量r賦值給另外一個接口變量w w = r.(io.Writer) // 接口變量w的pair與r的pair相同,即使w是空接口,pair也是不變的 fmt.Println(w) // interface以及pair的存在,是golang中實現反射的前提,理解了pair就更容易理解反射 // 反射就是用來檢測存儲在接口變量內部(值value,實際類型concrete type) pair對的一種機制 // 2. golang的反射reflect // reflect的基本功能 TypeOf ValueOf fmt.Println(reflect.TypeOf(15), reflect.ValueOf(15).Interface().(int)) // reflect.TypeOf是獲取pair中的type,reflect.ValueOf是獲取pair中的value // 也就是說反射可以將 接口類型的變量 轉換為 反射類型對象,反射值得是reflect.Type,reflect.Value這兩種 // 3. 從reflect.Value中獲取接口interface的信息 // 當執行reflect.ValueOf(interface)之后,就得到了一個類型為reflect.Value變量,可以通過它本身的Interface()方法 // 獲取接口變量的真是內容,然后可以根據類型判斷進行轉換,轉換為原有真實類型,我們可以已知原有類型,也有可能未知原有類型 // (1) 已知原有類型,進行強制轉換 // realValue := value.Interface().(已知的類型) // 示例如下 var f1 float32 = 3.14567 pointer := reflect.ValueOf(&f1) value := reflect.ValueOf(f1) // 反射將接口類型的變量轉換為反射類型的對象 reflect.Value類型 // 可以理解為強制轉換,需要注意的是, 轉換的時候,如果轉換的類型不完全符合,直接panic // go對類型要求非常嚴格,一定要完全符合 // 如下兩個,一個是*float32, 一個是float32,如果弄混直接報錯 // value.Interface() 將反射類型的對象轉換為接口類型的變量 // value.Interface().(float32) 斷言接口類型變量的具體類型 convertPointer := pointer.Interface().(*float32) convertValue := value.Interface().(float32) fmt.Println(convertPointer, convertValue) // 說明:轉換的時候,如果轉換的類型不完全符合,則直接panic,類型要求非常嚴格 // 轉換的時候要區分指針還是值 // 也就是說反射可以將反射類型的對象轉換為接口類型的變量 // (2) 未知原有類型,【遍歷探測其Filed】 // 案例 var user = User{1, "lisi", 18} DoFieldAndMethod(user) // 4. reflect.Value 設置實際變量的值 var f2 = math.Pi f2Value := reflect.ValueOf(&f2) // reflect.Value對象類型 // reflect.Value.Elem() 獲取原始值對應的反射對象,只有原始對象才能修改,當前反射對象是不能修改的 newValue := f2Value.Elem() // reflect.Value對象類型 fmt.Println(newValue.Type(), newValue.CanSet()) newValue.SetFloat(77) fmt.Println(f2) /* 說明 需要傳入的參數是* float64這個指針,然后可以通過pointer.Elem()去獲取所指向的Value,注意一定要是指針。 如果傳入的參數不是指針,而是變量,那么 通過Elem獲取原始值對應的對象則直接panic 通過CanSet方法查詢是否可以設置返回false newValue.CantSet()表示是否可以重新設置其值,如果輸出的是true則可修改,否則不能修改,修改完之后再進行打印發現真的已經修改了。 reflect.Value.Elem() 表示獲取原始值對應的反射對象,只有原始對象才能修改,當前反射對象是不能修改的 也就是說如果要修改反射類型對象,其值必須是“addressable”【對應的要傳入的是指針,同時要通過Elem方法獲取原始值對應的反射對象】 struct 或者 struct 的嵌套都是一樣的判斷處理方式 */ // 5. 通過reflect.ValueOf來進行方法的調用 // (1). 要通過反射來調用起對應的方法,必須要先通過reflect.ValueOf(interface)來獲取到reflect.Value,得到“反射類型對象”后才能做下一步處理 userValue := reflect.ValueOf(user) // 一定要指定參數為正確的方法名 // (2). 先看看帶有參數的調用方法 methodValue := userValue.MethodByName("ReflectCallFunc") args := []reflect.Value{reflect.ValueOf(11), reflect.ValueOf("wupeiqi")} methodValue.Call(args) // (3)在看看不帶參數的調用方法 methodValue = userValue.MethodByName("Call") args = make([]reflect.Value, 0) methodValue.Call(args) /* 說明 要通過反射來調用起對應的方法,必須要先通過reflect.ValueOf(interface)來獲取到reflect.Value,得到“反射類型對象”后才能做下一步處理 reflect.Value.MethodByName這.MethodByName,需要指定准確真實的方法名字,如果錯誤將直接panic,MethodByName返回一個函數值對應的reflect.Value方法的名字。 []reflect.Value,這個是最終需要調用的方法的參數,可以沒有或者一個或者多個,根據實際參數來定。 reflect.Value的 Call 這個方法,這個方法將最終調用真實的方法,參數務必保持一致,如果reflect.Value'Kind不是一個方法,那么將直接panic。 本來可以用u.ReflectCallFuncXXX直接調用的,但是如果要通過反射,那么首先要將方法注冊,也就是MethodByName,然后通過反射調用methodValue.Call */ // 6. Golang的反射reflect性能 //Golang的反射很慢,這個和它的API設計有關。在 java 里面,我們一般使用反射都是這樣來弄的。 type_ := reflect.TypeOf(user) field, _ := type_.FieldByName("Name") //這里取出來的 field 對象是 reflect.StructField 類型,但是它沒有辦法用來取得對應對象上的值。 //如果要取值,得用另外一套對object,而不是type的反射 fmt.Println(field.Name) // 打印的是Name value_ := reflect.ValueOf(user) fieldValue := value_.FieldByName("Name") // 這里取出來的 fieldValue 類型是 reflect.Value,它是一個具體的值,而不是一個可復用的反射對象了 // 每次反射都需要malloc這個reflect.Value結構體,並且還涉及到GC。 fmt.Println(fieldValue.Interface().(string)) /* 小結: Golang reflect慢主要有兩個原因 涉及到內存分配以及后續的GC; reflect實現里面有大量的枚舉,也就是for循環,比如類型之類的。 */ } type User struct { Id int Name string Age int } func (u User) ReflectCallFunc(a int, b string) { fmt.Println("reflectCallFunc is called!!!") } func (u User) Call() { fmt.Println("call") } func DoFieldAndMethod(user interface{}) { userType := reflect.TypeOf(user) userValue := reflect.ValueOf(user) // 注意:方法名和字段名首字母一定要大寫,否則獲取不到 // 遍歷獲取字段名、類型、值 for i := 0; i < userType.NumField(); i++ { field := userType.Field(i) value := userValue.Field(i).Interface() fmt.Println(field.Name, field.Type, value) } // 遍歷獲取方法名和方法類型 for i := 0; i < userType.NumMethod(); i++ { m := userType.Method(i) fmt.Println(m.Name, m.Type) } /* 說明 通過運行結果可以得知獲取未知類型的interface的具體變量及其類型的步驟為: 先獲取interface的reflect.Type,然后通過NumField進行遍歷 再通過reflect.Type的Field獲取其Field 最后通過Field的Interface()得到對應的value 通過運行結果可以得知獲取未知類型的interface的所屬方法(函數)的步驟為: 先獲取interface的reflect.Type,然后通過NumMethod進行遍歷 再分別通過reflect.Type的Method獲取對應的真實的方法(函數) 最后對結果取其Name和Type得知具體的方法名 也就是說反射可以將“反射類型對象”再重新轉換為“接口類型變量” struct 或者 struct 的嵌套都是一樣的判斷處理方式 */ }
參考鏈接:https://studygolang.com/articles/12348?fr=sidebar
反射中type和kind的區別