編程語言中反射的概念
在計算機科學領域,反射是指一類應用,它們可以自描述和自控制。 也就是說,這類應用經過採用某種機制來實現對自已行爲的描述(self-representation)和監測(examination), 並能根據自身行爲的狀態和結果,調整或修改應用所描述行爲的狀態和相關的語義。java
每種語言的反射模型都不一樣,而且有些語言根本不支持反射。 golang語言實現了反射,反射機制就是在運行時動態的調用對象的方法和屬性,官方自帶的reflect包就是反射相關的,只要包含這個包就可使用。golang
多插一句,golang的gRPC也是經過反射實現的。編程
interface和反射
在講反射以前,先來看看golang關於類型設計的一些原則框架
- 變量包括(type, value)兩部分
- 理解這一點就知道爲何 nil != nil 了
- type 包括static type和 concrete type. 簡單來講 static type是你在編碼是看見的類型(如int, string), concrete type是runtime系統看見的類型
- 類型斷言可否成功,取決於變量的concrete type, 而不是static type. 所以, 一個reader變量若是它是concrete type也實現了write方法的話,它也能夠被類型斷言爲writer。
接下來要講的反射,就是創建在類型之上的,Golang的指定類型的變量的類型是靜態的(也就是指定int、string這些的變量,它的type是static type) 在建立變量的時候就已經肯定,反射主要與Golang的interface類型相關(它的type是concrete type),只有interface類型纔有反射一說。編程語言
在Golang的實現中,每一個interface變量都有一個對應pair,pair中記錄了實際變量的值和類型.函數
(value, type)
value是實際變量值,type是實際變量的類型。一個Interface{}類型的變量包含了2個指針,一個指針指向值的類型[對應concrete type],另一個指針指向實際的值[對應value] 例如,建立類型爲 *os.File的變量,而後將其賦值給一個接口變量r:性能
tty, err := os.OpenFile("/dev/tty",os.O_RDWR,0) var r io.Reader r = tty
接口變量r的pair中將記錄以下信息: (tty, *os.File),即便w是空接口類型,pair也是不變的。 interface及其pair是存在,是Golang中實現反射的前提,理解pair,就更容易理解反射。反射就是用來檢測存儲在接口變量內部(值value; 類型concrete type) pair以的一種機制。編碼
Golang的反射reflect
reflect的基本功能TypeOf和ValueOf
既然反射就是用來檢測存儲在接口變量內部(值value; 類型concrete type)pair對的一種機制。那麼在golang的reflect反射包中有什麼樣的方式可讓咱們直接獲取到變量內部的信息呢? 它提供了兩種類型(或者說兩個方法) 讓咱們能夠很容易的訪問接口變量內容,分別是reflect.ValueOf()和reflect.TypeOf(),看看官方的解釋:spa
// ValueOf returns a new Value initialized to the concrete value // stored in the interface i. ValueOf(nil) returns the zero func ValueOf(i interface{}) Value {...} 翻譯一下:ValueOf用來獲取輸入參數接口中的數據的值,若是接口爲空則返回0 // TypeOf returns the reflection Type that represents the dynamic type of i. // If i is a nil interface value, TypeOf returns nil. func TypeOf(i interface{}) Type {...} 翻譯一下:TypeOf用來動態獲取輸入參數接口中的值的類型,若是接口爲空則返回nil
reflect.Type()是獲取pair中的type,reflect.ValueOf()獲取pair中的value,示例以下:翻譯
package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 fmt.Println("type: ", reflect.TypeOf(num)) fmt.Println("value: ",reflect.ValueOf(num)) } --- result: type: float64 value: 1.2345
說明
- 1.reflect.TypeOf: 直接給到了咱們想要的type類型,如float6四、int、各類pointer、struct等等真實的類型
- 2.reflect.ValueOf: 直接給到了咱們想要的具體的值,如1.2345這個具體數值,或者相似&{1 "Allen.Wu" 25}這樣的結構體struct的值
- 3.也就是說明反射能夠將"接口類型" 轉換爲 "反射類型對象",反射類型指的是reflect.Type和reflect.Value這兩種
從relfect.Value中獲取接口interface的信息
當執行reflect.ValueOf(interface)以後,就獲得一個類型爲"relfect.Value"變量, 能夠經過它自己的interface() 方法得到接口變量的真實內容 而後能夠經過類型判斷進行轉換,轉換爲原有真實類型。不過,咱們可有是已知原有類型,也有多是未知原有類型,所以,下面分兩種狀況進行說明。
已知原有類型[進行"強制轉換"]
已知類型後轉換爲其對應的類型的作法以下,直接經過interface方法而後強制轉換,以下:
realValue := value.Interface().(已知的類型)
示例以下:
package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 pointer := reflect.ValueOf(&num) value := reflect.ValueOf(num) // 能夠理解爲"強制轉換",可是須要注意的時候,轉換的時候,若是轉換的類型不徹底符合,則直接panic // golang 對類型要求很是嚴格, 類型必定要徹底符合 // 以下兩個,一個是 *float64,一個是float64,若是弄混,則會panic convertPointer := pointer.Interface().(*float64) convertValue := value.Interface().(float64) fmt.Println(convertPointer) fmt.Println(convertValue) } --- result: 0xc42000e238 1.2345
說明
- 1.轉換的時候,若是轉換的類型不徹底符合,則直接panic,類型要求很是嚴格!
- 2.轉換的時候,要區分是指針仍是值
- 3.也就是說反射能夠將"反射類型對象"再從新轉換爲"接口類型變量"
未知原有類型[遍歷探測其filed]
不少狀況下,咱們可能並不知道其具體類型,那麼這個時候,該如何作呢?須要咱們進行遍歷探測其Filed來得知,示例以下:
package main import ( "fmt" "reflect" ) type User struct { Id int Name string Age int } func (u User) ReflectCallFunc() { fmt.Println("Allen.Wu ReflectCallFunc") } func main() { user := User{1, "Allen.Wu", 25} DoFiledAndMethod(user) } // 經過接口來獲取任意參數,而後一一揭曉 func DoFiledAndMethod(input interface{}) { getType := reflect.TypeOf(input) fmt.Println("get Type is :", getType.Name()) getValue := reflect.ValueOf(input) fmt.Println("get all Fields is:",getValue) // 獲取方法字段 // 1. 先獲取interface的reflect.Type,而後經過NumField進行遍歷 // 2. 再經過reflect.Type的Field獲取其Field // 3. 最後經過Field的Interface()獲得對應的value for i := 0; i < getType.NumField(); i++ { field := getType.Field(i) value := getValue.Field(i).Interface() fmt.Printf("%s: %v = %v\n",field.Name, field.Type, value) } // 獲取方法 // 1.先獲取interface的reflect.Type, 而後經過.NumMethod進行遍歷 for i := 0; i < getType.NumMethod(); i++ { m := getType.Method(i) fmt.Printf("%s: %v\n",m.Name, m.Type) } } --- result: get Type is : User get all Fields is: {1 Allen.Wu 25} Id: int = 1 Name: string = Allen.Wu Age: int = 25 ReflectCallFunc: func(main.User)
說明
經過運行結果能夠得知獲取未知類型的interface的具體變量及其類型的步驟爲:
- 1.先獲取interface的reflect.Type,而後經過NumField進行遍歷
- 2.再經過reflect.Type的Field獲取其Field
- 3.最後經過Field的interface()獲得對應的value
經過運行結果能夠得知獲取未知類型的interface的所屬方法(函數)的步驟爲:
- 1.先獲取interface的reflect.Type,而後經過NumMethod進行遍歷
- 2.再分別經過reflect.Type的Method獲取對應的真實的方法(函數)
- 3.最後對結果取其Name和Type得知具體的方法名
- 4.也就是說反射能夠將"反射類型對象" 再從新轉換爲"接口類型變量"
- 5.struct或者struct的嵌套都是同樣的判斷處理方式。
經過reflect.Value設置實際變量的值
reflect.Value是經過reflect.ValueOf(x)得到的,只有當X是指針的時候,才能夠經過reflect.Value修改實際變量X值,即: 要修改反射類型的對象就必定要保證其值是"addressable"的。 示例以下:
package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 fmt.Println("old value of pointer:", num) // 經過reflect.ValueOf獲取num中的reflect.Value,注意,參數必須是指針才能修改其值 pointer := reflect.ValueOf(&num) newValue := pointer.Elem() fmt.Println("type of pointer:", newValue.Type()) fmt.Println("settablility of pointer:", newValue.CanSet()) // 從新賦值 nweValue.SetFloat(77) fmt.Println("new value of pointer:", num) /* 若是reflect.ValueOf的參數不是指針,會如何? pointer = reflect.ValueOf(num) newValue = pointer.Elem() 若是非指針,這裏直接panic, "panic: reflect: call of reflect.Value.Elem on float64 Value" */ } ------ result: old value of pointer: 1.2345 type of pointer: float64 settability of pointer: true new value of pointer: 77
說明
- 1.須要傳入的參數是 *float64這個指針,而後能夠經過pointer.Elem()去獲取所指向的Value,注意必定要是指針。
- 2.若是傳入的參數不是指針,而是變量,那麼 a.經過Elem獲取原始值對應的對象則直接panic b.經過CanSet方法查詢是否能夠設置返回false
- 3.newValue.CantSet()表示是否能夠從新設置其值,若是輸出的是true則可修改,不然不能修改,修改完以後再進行打印發現真的已經修改了。
- 4.reflect.Value.Elme()表示獲取原始值對應的反射對象,只有原始對象才能修改,當前反射對象是不能修改的
- 5.也就是說若是要修改反射類型對象,其值必須是"addressable" 【對應的要傳入的是指針,同時要經過Elem方法獲取原始值對應的反射對象】
- 6.struct或者struct的嵌套都是同樣的判斷處理方式
經過reflect.ValueOf來進行方法調用
這算是一個高級用法了,前面咱們只說到對類型、變量的幾種反射的用法,包括如何獲取其值、其類型、若是從新設置新值。 可是在工程應用中,另一個經常使用而且屬於高級的用法,就是經過reflect來進行方法函數的調用。 好比咱們要作框架工程的時候,須要能夠隨意擴展方法,或者說用戶能夠自定義方法,那麼咱們經過什麼手段讓用戶可以自定義呢? 關鍵點在於用戶的自定義方法是未可知的,所以咱們能夠經過reflect來搞定
示例以下:
package main import ( "fmt" "reflect" ) type User struct { Id int Name string Age int } func (u User) ReflectCallFuncHasArgs(name string, age int) { fmt.Println("ReflectCallFuncHasArgs name: ",name,", age:", age, "and origal User.Name:", u.Name) } func (u User) ReflectCallFuncNoArgs() { fmt.Println("ReflectCallFuncNoArgs") } // 如何經過反射來進行方法的調用? // 原本能夠用u.ReflectCallFuncXXX直接調用的,可是若是要經過反射,那麼首先要將方法註冊,也就是MethodByName,而後經過反射調動mv.Call fucn main() { user := User{1, "Allen.Wu", 25} // 1.要經過反射來調用起對應的方法,必需要經過reflect.ValueOf(interface)來獲取到reflect.Value,獲得"反射類型對象"後才能作下一步處一 getValue := reflect.ValueOf(user) // 必定要指定參數爲正確的方法名 // 2.先看看帶有參數的調用方法 methodValue := getValue.MethodByName("ReflectCallFuncHasArgs") args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)} methodValue.Call(args) // 必定要指定參數爲正確的方法名 // 3.再看看無參數的調用方法 methodValue = getValue.MethodByName("ReflectCallFuncNoArgs") args = make([]reflect.Value, 0) methodValue.Call(args) } --- result: ReflectCallFuncHasArgs name: wudebao , age: 30 and origal User.Name: Allen.Wu ReflectCallFuncNoArgs
說明
- 1.要經過反射來調用起對應的方法,必需要先經過reflect.ValueOf(interface)來獲取到reflect.Value,獲得"反射類型對象"後才能作下一步處理
- 2.reflect.Value.MethodByName,須要指定準確真實的方法名字,若是錯誤將直接panic,MethodByName返回一個函數值對應的reflect.Value方法的名字
- 3.[]reflect.Value,這個是最終須要調用的方法的參數,能夠沒有或者一個或者多個,根據實際參數來定。
- 4.reflect.Value的Call這個方法,這個方法最終調用真實的方法,參數務必保持一致,若是reflect.Value'Kind不是一個方法,那麼將直接panic。
- 5.原本能夠用u.ReflectCallFuncXXX直接調用的,可是若是要經過反射,那麼首先要將方法註冊,也就是MethodByName,而後經過反射調用methodValue.Call
Golang的反射reflect性能
Golang的反射很慢,這個和它的API設計有關。在java裏面,咱們通常使用反射都是這樣來弄的。
Field field = clazz.getField("hello"); field.get(obj1); field.get(obj2);
這個取得的反射對象類型是 java.lang.reflect.Field。它是能夠複用的。只要傳入不一樣的obj,就能夠取微電影這個obj上對應的field。 可是Golang的反射不是這樣設計的:
type, _ := reflect.TypeOf(obj) field, _ := type_.FieldByName("hello")
這裏取出來的field對象是reflect.StructField類型,可是它沒有辦法用來取得對應對象上的值。若是要取值,得用另一套對object,而不是type的反射。
type, _ := reflect.ValueOf(obj) fieldValue := type_.FieldByName("hello")
這裏取出來的fieldValue類型是reflect.Value,它是一個具體的值,而不是一個可複用的反射對象上的值。若是要取值,得用另一套object,而不是type的反射。
type_ := reflect.ValueOf(obj) fieldValue := type_.FieldByName("hello")
這裏取出來的fieldValue類型是reflect.Value,它是一個具體的值,而不是一個可複用的反射對象了,每次反射reflect.Value結構體,而且還涉及到GC。
小結
golang reflect慢主要有兩個緣由
- 1.涉及到內存分配以及後續的GC;
- 2.reflect實現裏面有大量的枚舉,也就是for循環,好比類型之類的。
總結
上述詳細說明了Golang的反射reflect的各類功能和用法,都附帶有相應的示例,相信可以在工程應用中進行相應實踐,總結一下就是:
-
反射能夠大大提升程序的靈活性,使得interface{}有更大的發揮餘地 1.反射必須結合interface才玩得轉 2.變量的type要是concrete type的(也就是interface變量) 纔有反射一說
-
反射能夠將"接口類型變量"轉換爲"反射類型對象" 1.反射使用TypeOf和ValueOf函數從接口中獲取目標對象信息
-
反射能夠將"反射類型對象"轉換爲"接口類型變量" 1.reflect.value.Interface().(已知的類型) 2.遍歷reflect.Type的Field獲取其Field
-
反射能夠修改反射類型對象,可是其值必須是"addressable" 1.想要利用反射修改對象狀態,前提是interface.data是 settable,即 pointer-interface
-
經過反射能夠"動態"調用方法
-
由於Golang自己不支持模板,所以在以往須要使用模板的場景下每每就須要使用反射(reflect)來實現.