Go進階:反射3定律

各位學習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.Readerspa

最特別:空接口 interface{}的變量能夠被任何類型的值賦值,但類型一直都是 interface{}

接口的表示

Russ Cox(Go語言創始人)在他的博客詳細介紹了Go語言接口,結論是:指針

接口類型的變量存儲的是一對數據

  1. 變量實際的值
  2. 變量的靜態類型

例子:

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.

反射的3條定律

定律1:從接口值到反射對象

反射是一種檢測存儲在接口變量中值和類型的機制。經過reflect包的一些函數,能夠把接口轉換爲反射定義的對象。

掌握reflect包的如下函數:

  1. reflect.ValueOf({}interface) reflect.Value:獲取某個變量的值,但值是經過reflect.Value對象描述的。
  2. reflect.TypeOf({}interface) reflect.Type:獲取某個變量的靜態類型,但值是經過reflect.Type對象描述的,是能夠直接使用Println打印的。
  3. reflect.Value.Kind() Kind:獲取變量值的底層類型(類別),注意不是類型,是Int、Float,仍是Struct,仍是Slice,具體見此
  4. 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:從反射對象到接口值

定律2是定律1的逆向過程,上面咱們學了:普通變量 -> 接口變量 -> 反射對象的過程,這是從反射對象 -> 接口變量的過程,使用的是ValueInterface函數,是把實際的值賦值給空接口變量,它的聲明以下:

func (v Value) Interface() (i interface{})

回憶一下:接口變量存儲了實際的值和值的類型,Println能夠根據接口變量實際存儲的類型自動識別其值並打印。

注意事項:若是Value是結構體的非導出字段,調用該函數會致使panic。

定律3:當反射對象所存的值是可設置時,反射對象纔可修改

從定律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中原始對象的值。

系列函數有:

  • Value.SetInt()
  • Value.SetUint()
  • Value.SetBool()
  • Value.SetBytes()
  • Value.SetFloat()
  • Value.SetString()
  • ...

設置函數這麼多,到底該選用哪一個Set函數?
根據Value.Kind()的結果去得到變量的底層類型,而後選用該類別的Set函數。

參考資料

  1. https://blog.golang.org/laws-of-reflection
  1. 若是這篇文章對你有幫助,請點個贊/喜歡,感謝
  2. 本文做者:大彬
  3. 若是喜歡本文,隨意轉載,但請保留此原文連接:http://lessisbetter.site/2019/02/24/go-law-of-reflect/

相關文章
相關標籤/搜索