各位學習Go語言的朋友,週末好,此次跟你們聊一聊Go語言的一個高級話題:反射。html
這篇文章是從我過去的學習筆記修改來的,內容主要來自Go Blog的一篇文章《The law of reflection》。golang
這篇文章主要介紹反射和接口的關係,解釋內在的關係和原理。編程
反射來自元編程,指經過類型檢查變量自己數據結構的方式,只有部分編程語言支持反射。數據結構
反射構建在類型系統之上,Go是靜態類型語言,每個變量都有靜態類型,在編譯時就肯定下來了。less
好比:編程語言
type MyInt int var i int var j MyInt
i和j的底層類型都是int
,但i的靜態類型是int
,j的靜態類型是MyInt
,這兩個是不一樣類型,是不能直接賦值的,須要類型強制轉換。函數
接口類型比較特殊,接口類型的變量被多種對象類型賦值,看起來像動態語言的特性,但變量類型始終是接口類型,Go是靜態的。舉例:學習
var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer) // and so on
雖然r被3種類型的變量賦值,但r的類型始終是io.Reader
。spa
最特別:空接口interface{}
的變量能夠被任何類型的值賦值,但類型一直都是interface{}
。
Russ Cox(Go語言創始人)在他的博客詳細介紹了Go語言接口,結論是:指針
接口類型的變量存儲的是一對數據:
例子:
var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty
r是接口類型變量,保存了值tty和tty的類型*os.File
,因此才能使用類型斷言判斷r保存的值的靜態類型:
var w io.Writer w = r.(io.Writer)
雖然r中包含了tty和它的類型,包含了tty的全部函數,但r是接口類型,決定了r只能調用接口io.Reader
中包含的函數。
記住:接口變量保存的不是接口類型的值,仍是英語提及來更方便:Interfaces do not hold interface values.
反射是一種檢測存儲在接口變量中值和類型的機制。經過reflect
包的一些函數,能夠把接口轉換爲反射定義的對象。
掌握reflect
包的如下函數:
reflect.ValueOf({}interface) reflect.Value
:獲取某個變量的值,但值是經過reflect.Value
對象描述的。reflect.TypeOf({}interface) reflect.Type
:獲取某個變量的靜態類型,但值是經過reflect.Type
對象描述的,是能夠直接使用Println
打印的。reflect.Value.Kind() Kind
:獲取變量值的底層類型(類別),注意不是類型,是Int、Float,仍是Struct,仍是Slice,具體見此。reflect.Value.Type() reflect.Type
:獲取變量值的類型,效果等同於reflect.TypeOf
。再解釋下Kind和Type的區別,好比:
type MyInt int var x MyInt = 7 v := reflect.ValueOf(x)
v.Kind()獲得的是Int,而Type獲得是MyInt
。
定律2是定律1的逆向過程,上面咱們學了:普通變量 -> 接口變量 -> 反射對象
的過程,這是從反射對象 -> 接口變量
的過程,使用的是Value
的Interface
函數,是把實際的值賦值給空接口變量,它的聲明以下:
func (v Value) Interface() (i interface{})
回憶一下:接口變量存儲了實際的值和值的類型,Println
能夠根據接口變量實際存儲的類型自動識別其值並打印。
注意事項:若是Value是結構體的非導出字段,調用該函數會致使panic。
從定律1入手理解,定律3就再也不那麼難懂。
Settability is a property of a reflection Value, and not all reflection Values have it.
可設置指的是,能夠經過Value設置原始變量的值。
經過函數的例子思考一下可設置:
func f(x int)
在調用f的時候,傳入了參數x,從函數內部修改x的值,外部的變量的值並不會發生改變,由於這種是傳值,是拷貝的傳遞方式。
func f(p *int)
函數f的入參是指針類型,在函數內部的修改變量的值,函數外部變量的值也會跟着變化。
使用反射也是這個原理,若是建立Value時傳遞的是變量,則Value是不可設置的。若是建立Value時傳遞的是變量地址,則Value是可設置的。
可使用Value.CanSet()
檢測是否能夠經過此Value修改原始變量的值。
x := 10 v1 := reflect.ValueOf(x) fmt.Println("setable:", v1.CanSet()) p := reflect.ValueOf(&x) fmt.Println("setable:", p.CanSet()) v2 := p.Elem() fmt.Println("setable:", v2.CanSet())
如何經過Value設置原始對象值呢?
Value.SetXXX()
系列函數可設置Value中原始對象的值。
系列函數有:
設置函數這麼多,到底該選用哪一個Set函數?
根據Value.Kind()
的結果去得到變量的底層類型,而後選用該類別的Set函數。
- 若是這篇文章對你有幫助,請點個贊/喜歡,感謝。
- 本文做者:大彬
- 若是喜歡本文,隨意轉載,但請保留此原文連接:http://lessisbetter.site/2019/02/24/go-law-of-reflect/