Go進階:反射3定律

各位學習Go語言的朋友,週末好,這次跟大家聊一聊Go語言的一個高級話題:反射。

這篇文章是從我過去的學習筆記修改來的,內容主要來自Go Blog的一篇文章《The law of reflection》。

這篇文章主要介紹反射和接口的關係,解釋內在的關係和原理

反射來自元編程,指通過類型檢查變量本身數據結構的方式,只有部分編程語言支持反射。

類型

反射構建在類型系統之上,Go是靜態類型語言,每一個變量都有靜態類型,在編譯時就確定下來了。

比如:

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

最特別:空接口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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章