這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
聽 聽 反射是在golang程式運行時檢查變數所具有類型的一種機制。由於反射可以得出關於變數結構的資料(即“關於資料的資料”),所以這也被認為是golang元編程的基礎。初學反射,會感覺有些“玄乎”。我這裡由淺入深,嘗試闡述反射內涵,並解讀反射三法則(http://blog.golang.org/laws-of-reflection)。
0 從類型和方法理解反射內涵
聽 聽 在基本的層面上,反射只是一個檢查儲存在介面變數中的類型和值的演算法。使用反射機制,首先需要匯入reflect包,reflect包中有兩個重要類型需要瞭解,reflect.Type和reflect.Value,這兩個類型使得可以訪問變數的內容。與此相關的,還有兩個簡單的函數,reflect.TypeOf和reflect.ValueOf,可以從介面值中分別擷取reflect.Type和reflect.Value。
聽 聽 初學可能會認為reflect.Type和reflect.Value是一種並列關係,但其實它們是一種內含項目關聯性,我們結合一段代碼來理解這段話。
import聽(聽聽聽"fmt"聽聽聽"reflect")聽func聽main()聽{聽聽聽var聽x聽float64聽=聽1.1聽聽聽fmt.Println("reflect.Value:",聽reflect.ValueOf(x))聽聽聽fmt.Println("reflect.Type:",聽reflect.TypeOf(x))聽聽聽v聽:=聽reflect.ValueOf(x)聽聽聽fmt.Println("reflect.Type:",v.Type())聽聽聽fmt.Println("actual聽value:",聽v.Float())聽聽聽fmt.Println("kind聽is聽float64?",聽v.Kind()聽==聽reflect.Float64)}
其輸出為:
聽 聽 根據程式及其結果,我們可以發現:在go語言中,每個值都包含兩個內容:類型和實際的值。從類型角度來看,reflect.Value是一個關於<類型, 實際的值>的二元組,而reflect.Type是值的類型,二者是內含項目關聯性。從方法角度來看,reflect.TypeOf 和 (reflect.ValueOf(x)).Type都可以返回reflect.Type;(reflect.ValueOf(x)).Float可以返回實際的值(類似的方法還包括(reflect.ValueOf(x)).Int、(reflect.ValueOf(x)).Bool等);(reflect.ValueOf(x)).Kind可以返回一個常量定義的類型。
聽 聽 根據上述分析,我們可以得出一個,更為直觀形象的表明值、類型、實際的值的關係。
聽 聽 此外,golang採用靜態類型機制,TypeOf返回靜態類型;但是,Kind返回底層類型。我們同樣以一段代碼來驗證這段話。
import聽(聽聽聽"fmt"聽聽聽"reflect")聽type聽MyInt聽int聽func聽main()聽{聽聽聽var聽x聽MyInt聽=聽1聽聽聽v聽:=聽reflect.ValueOf(x)聽聽聽fmt.Println("reflect.Type:",聽v.Type())聽聽聽fmt.Println("kind聽is聽int?",聽v.Kind()聽==聽reflect.Int)}
輸出:
1 法則一:從介面值到反射對象的反射(Reflection goes from interface value toreflection object)
聽 聽 前文所述內容其實就是從介面值到反射對象的反射,代表方法為reflect.ValueOf和reflect.TypeOf。可能有人會問,介面?介面在哪呢?我們來看一些前文提到這兩個函數的聲明,函數的參數是空介面,其實介面就在那裡。關於golang的介面,大家可以參見我的另一篇博文《Golang中的介面》。
func聽ValueOf(i聽interface{})聽Valuefunc聽TypeOf(i聽interface{})聽Type
2 法則二:從反射對象到介面值的反射(Reflection goes from reflection object to interface value)
聽 聽聽從reflect.Value可以使用Interface方法還原介面值;此方法可以高效地打包類型和值資訊到介面表達中,並返回這個結果。方法聲明:
func聽(v聽Value)聽Interface()聽interface{}
聽 聽 通過反射對象 v 可以列印 float64 的表達值。
y聽:=v.Interface().(float64)聽//聽y聽將為類型聽float64。fmt.Println(y)
聽 聽 還有更為簡潔的實現。fmt.Println,fmt.Printf等其他所有傳遞一個空介面值作為參數的函數,在 fmt包內部解包的方式就像之前的例子這樣。因此正確的列印reflect.Value的內容的方法就是將Interface方法的結果進行格式化列印(formatted print routine).聽
fmt.Println(v.Interface())
聽 聽 為什麼不是fmt.Println(v)?因為v是一個 reflect.Value;這裡希望獲得的是它儲存的實際的值。
聽 聽 我們修改前文代碼還進行驗證:
func聽main()聽{聽聽聽var聽x聽float64聽=聽1.1聽聽聽fmt.Println("reflect.Value:",聽reflect.ValueOf(x))聽聽聽fmt.Println("reflect.Type:",聽reflect.TypeOf(x))聽聽聽v聽:=聽(reflect.ValueOf(x))聽聽聽fmt.Println("reflect.Type:",聽v.Type())聽聽聽fmt.Println("actual聽value(interface):",聽v.Interface())聽聽聽fmt.Println("kind聽is聽float64?",聽v.Kind()聽==聽reflect.Float64)}
其輸出:
聽 聽 進一步地,我們可以修改上述關係,新圖更為簡潔優雅:
3. 為了修改反射對象,其值必須可設定(To modify a reflectionobject, the value must be settable)
聽 聽 反射對象可以通過SetFloat等方法設定值,通過CanSet判斷可設定性。但是這裡面有坑,有些值是不可設定的。我們還是通過一段代碼來看。
import聽(聽聽聽聽"fmt"聽聽聽聽"reflect")聽func聽main()聽{聽聽聽聽var聽x聽float64聽=聽1.1聽聽聽聽v聽:=聽reflect.ValueOf(x)聽聽聽聽fmt.Println("settability聽of聽v:",v.CanSet())聽聽聽聽v.SetFloat(1.2)}
其輸出
聽 聽 其結果表明,反射對象v是不可設定的,如果硬要設定的話,會有panic異常。
聽 聽 為什麼不能設定呢?我們可以從函數傳參的角度來思考這個問題。V := reflect.ValueOf(x),這個函數是值傳遞,即傳遞了一個x的副本到函數中,而非x本身。我們都知道,值傳遞的參數是不能被真正修改的。
聽 聽 我最初還有過這樣的想法:v和x又不是一個變數,x不能被修改,但是v應該可以被修改啊。完全從形式上考慮,這樣似乎有道理。但是再多想一層,如果允許執行,雖然v可以被修改,但是卻無法更新x。也就是說,在反射值內部允許修改x的副本,但是x本身卻不會受到這個影響。這會造成混亂,並且毫無意義,因此在golang中這樣操作是非法的。
聽 聽 讓我們重新用函數傳參的角度思考這個問題。如果傳遞副本不能修改,那我們就通過就傳遞指標好了。我們來試試:
func聽main()聽{聽聽聽聽var聽x聽float64聽=聽1.1聽聽聽聽p聽:=聽reflect.ValueOf(&x)聽聽聽聽fmt.Println("type聽of聽p:",p.Type())聽聽聽聽fmt.Println("settability聽of聽p:",p.CanSet())}
聽 聽 還是不行。因為p的實際類型是*float64,而非float64,這樣修改相當於要直接修改地址了。
聽 聽 我們可以藉助Elem方法,通過指標來修改指標指向的具體值。
func聽(v聽Value)Elem()聽Value//Elem聽returns聽the聽value聽that聽the聽interface聽v聽contains聽or聽that聽the聽pointer聽vpoints聽to.聽It聽panics聽if聽v's聽Kind聽is聽not聽Interface聽or聽Ptr.聽It聽returns聽the聽zeroValue聽if聽v聽is聽nil.
func聽main()聽{聽聽聽聽var聽x聽float64聽=聽1.1聽聽聽聽p聽:=聽reflect.ValueOf(&x)聽聽聽聽fmt.Println("type聽of聽p:",p.Type())聽聽聽聽v聽:=聽p.Elem()聽聽聽聽fmt.Println("type聽of聽v:",v.Type())聽聽聽聽fmt.Println("settability聽of聽v:",v.CanSet())}
其輸出
聽 聽 聽 這樣就可以進行修改了。雖然p是不可修改的,但是v可以修改。這種方法思路上類似引用傳參,傳入地址,修改地址所指向的具體值。
參考
http://blog.golang.org/laws-of-reflection
http://www.tuicool.com/articles/VFj6ze
http://studygolang.com/articles/1468
本文出自 “說話的白菜” 部落格,謝絕轉載!