golang反射與反射三法則

    反射是在golang程序運行時檢查變量所具備類型的一種機制。因爲反射能夠得出關於變量結構的數據(即關於數據的數據),因此這也被認爲是golang元編程的基礎。初學反射,會感受有些「玄乎」。我這裏由淺入深,嘗試闡述反射內涵,並解讀反射三法則(http://blog.golang.org/laws-of-reflectiongolang


0 從類型和方法理解反射內涵算法

    在基本的層面上,反射只是一個檢查存儲在接口變量中的類型和值的算法。使用反射機制,首先須要導入reflect包,reflect包中有兩個重要類型須要瞭解,reflect.Typereflect.Value,這兩個類型使得能夠訪問變量的內容。與此相關的,還有兩個簡單的函數,reflect.TypeOfreflect.ValueOf,能夠從接口值中分別獲取reflect.Typereflect.Value編程

    初學可能會認爲reflect.Typereflect.Value是一種並列關係,但其實它們是一種包含關係,咱們結合一段代碼來理解這段話。
ide

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)
}

其輸出爲:函數

wKioL1YxopehtIxkAACegkwOzhg143.jpg

    根據程序及其結果,咱們能夠發現:在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能夠返回一個常量定義的類型。ui

    根據上述分析,咱們能夠得出一個示意圖,更爲直觀形象的代表值、類型、實際的值的關係。spa

wKioL1Yx5MzRtdBiAAEXPOux5p8508.jpg

    此外,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)
}

輸出:orm

wKiom1YxoxKQQFAzAABrx5USwwo751.jpg


1 法則一:從接口值到反射對象的反射(Reflection goes from interface value toreflection object)對象

    前文所述內容其實就是從接口值到反射對象的反射,表明方法爲reflect.ValueOfreflect.TypeOf。可能有人會問,接口?接口在哪呢?咱們來看一些前文提到這兩個函數的聲明,函數的參數是空接口,其實接口就在那裏。關於golang的接口,你們能夠參見個人另外一篇博文《Golang中的接口》。

func ValueOf(i interface{}) Value
func 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)
}

其輸出:

wKioL1YxpHGgRbc8AACqUduMpKM284.jpg

    進一步地,咱們能夠修改上述關係示意圖,新圖更爲簡潔優雅:

wKiom1Yx5KyB-6Y_AAEc8sb5uKQ756.jpg


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)
}

其輸出

wKioL1YxpQrBY-onAAIabYSpE_Q032.jpg


    其結果代表,反射對象v是不可設置的,若是硬要設置的話,會有panic異常。

    爲何不能設置呢?咱們能夠從函數傳參的角度來思考這個問題。V := reflect.ValueOf(x),這個函數是值傳遞,即傳遞了一個x的副本到函數中,而非x自己。咱們都知道,值傳遞的參數是不能被真正修改的。

    我最初還有過這樣的想法:vx又不是一個變量,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())
}

wKiom1YxpTOB-8JJAAB6QfJi1gA741.jpg

    仍是不行。由於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())
}

其輸出

wKioL1YxpeaQJjobAACWdkY8w_E223.jpg

      這樣就能夠進行修改了。雖然p是不可修改的,可是v能夠修改。這種方法思路上相似引用傳參,傳入地址,修改地址所指向的具體值。


參考

http://blog.golang.org/laws-of-reflection

http://www.tuicool.com/articles/VFj6ze

http://studygolang.com/articles/1468

相關文章
相關標籤/搜索