圖解go反射實現原理

Go反射的實現和interface和unsafe.Pointer密切相關。如果對golang的interface底層實現還沒有理解,可以去看我之前的文章:Go語言interface底層實現,unsafe.Pointer會在後續的文章中做介紹。(本文目前使用的Go環境是Go 1.12.9)

interface回顧

首先我們簡單的回顧一下interface的結構,總體上是:

圖解go反射實現原理


細分下來分為有函數的iface和無函數的eface(就是interface{});

無函數的eface

圖解go反射實現原理


有函數的iface

圖解go反射實現原理


靜態類型(static interface type)和動態混合類型(dynamic concrete type)

Go語言中,每個變量都有唯一個

靜態類型,這個類型是編譯階段就可以確定的。有的變量可能除了靜態類型之外,還會有動態混合類型

例如以下例子:

<code>//帶函數的interfacevar r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)if err != nil {    return nil, err}r = tty//不帶函數的interfacevar empty interface{}empty = tty/<code>

有函數的iface的例子

我們一句一句來看:第1行,var r io.Reader

圖解go反射實現原理


第4行至第7行就是簡單的賦值,得到一個*os.File的實例,暫且不看了。

最後一句第十句r = tty

圖解go反射實現原理


無函數的eface的例子

我們接著往下看,var empty interface{}

圖解go反射實現原理


最後是empty = tty

圖解go反射實現原理


但是記住:雖然有動態混合類型,但是對外"表現"依然是靜態類型。

Go反射簡介

Go反射有三大法則:

<code>//接口數據  =====》 反射對象1. Reflection goes from interface value to reflection object.//反射對象 ===> 接口數據2. Reflection goes from reflection object to interface value.// 倘若數據可更改,可通過反射對象來修改它3. To modify a reflection object, the value must be settable.  /<code>

Go 的反射就是對以上三項法則的實現。

Go的反射主要由兩部分組成:Type和Value,Type和Value是倆結構體:(這倆結構體具體內容可以略過不看,知道有這回事兒就行了)

Type:

<code>type Type interface {    Align() int    FieldAlign() int    Method(int) Method    MethodByName(string) (Method, bool)    NumMethod() int    Name() string    PkgPath() string    Size() uintptr    String() string    Kind() Kind    Implements(u Type) bool    AssignableTo(u Type) bool    ConvertibleTo(u Type) bool    Comparable() bool    Bits() int    ChanDir() ChanDir    IsVariadic() bool    Elem() Type    Field(i int) StructField    FieldByIndex(index []int) StructField    FieldByName(name string) (StructField, bool)    FieldByNameFunc(match func(string) bool) (StructField, bool)    In(i int) Type    Key() Type    Len() int    NumField() int    NumIn() int    NumOut() int    Out(i int) Type    common() *rtype    uncommon() *uncommonType}/<code>

Value:

<code>type Value struct {    typ *rtype    ptr unsafe.Pointer    flag}/<code>

你會發現反射的實現和interface的組成很相似,都是由“類型”和“數據值”構成,但是值得注意的是:interface的“類型”和“數據值”是在“一起的”,而反射的“類型”和“數據值”是分開的。

Type和Value提供了非常多的方法:例如獲取對象的屬性列表、獲取和修改某個屬性的值、對象所屬結構體的名字、對象的底層類型(underlying type)等等

Go中的反射,在使用中最核心的就兩個函數:

  • reflect.TypeOf(x)
  • reflect.ValueOf(x)

這兩個函數可以分別將給定的數據對象轉化為以上的Type和Value。這兩個都叫做反射對象

Reflection goes from interface value to reflection object(法則一)

給定一個數據對象,可以將數據對象轉化為反射對象Type和Value。

圖解go反射實現原理


事例代碼:

<code>package mainimport (    "fmt"    "reflect")func main() {    var x float64 = 3.4    t := reflect.TypeOf(x)    v := reflect.ValueOf(x)    fmt.Println("type:", t)   //type: float64    fmt.Println("value:", v.String())  //value: <float64>    fmt.Println("type:", v.Type()) // type: float64    fmt.Println("kind is float64:", v.Kind() == reflect.Float64) //kind is float64: true    fmt.Println("value:", v.Float()) //value: 3.4}/<float64>/<code>

由代碼17行可以看出:Value還可以獲取到當前數據值的Type。所以,法則一的圖應為:

圖解go反射實現原理


Reflection goes from reflection object to interface value.(法則二)

給定的反射對象,可以轉化為某種類型的數據對象。即法則一的逆向。

圖解go反射實現原理


注意Type是沒法逆向轉換的,仔細想想也合理,如果可逆類型轉化成什麼呢?(#.#)

承接法則一的代碼:

<code>package mainimport (    "fmt"    "reflect")func main() {    var x float64 = 3.4    t := reflect.TypeOf(x)    v := reflect.ValueOf(x)    ...    o := v.Interface().(float64) // 法則2代碼    fmt.Println(o)}/<code>

To modify a reflection object, the value must be settable.(法則三)

法則三是說:通過反射對象,可以修改原數據中的內容。

這裡說的反射對象,是指Value,畢竟Type只是表示原數據的類型相關的內容,而Value是對應著原數據對象本身。

在目前以上的所有例子中,反射得到的Value對象的修改,都是無法直接修改原數據對象的。

<code>package mainimport (    "fmt"    "reflect")func main() {    var x float64 = 3.4    t := reflect.TypeOf(x)    v := reflect.ValueOf(&x)    ....    o := v.Interface().(float64)    fmt.Println(o)    v.SetFloat(5.4) //此行會報錯    fmt.Println(x)}/<code>

這段代碼20行會報一個panic

<code>reflect: reflect.Value.SetFloat using unaddressable value/<code>

這句話的意思並不是地址不可達,而是:對象v不可設置(settable)。

我們可以通過Value結構體的CanSet()方法來查看是否可以設置修改新值。通過以下代碼可以知道CanSet()返回值是false。

<code>fmt.Println(v.CanSet()) // false/<code>

如何通過反射對象來修改原數據對象的值呢?

如何才能可以通過反射對象來修改原數據對象的值或者說為什麼不能設置呢?

原因簡單且純粹:在Go中,任何函數的參數都是值的拷貝,而非原數據。

反射函數reflect.ValueOf()也不例外。我們目前得到的反射對象,都是原對象的copy的反射對象,而非原對象本身,所以不可以修改到原對象;即使可以修改,修改一個傳參時候的副本,也毫無意義,不如報錯兒。Go反射第三法則中的制定的settable屬性就由此而來,還延伸出了類似於CanSet()的方法。

那如何修改呢?

首先,在Go中要想讓函數“有副作用“,傳值必須傳指針類型的。

<code>    ...    var x float64 = 3.4    v := reflect.ValueOf(&x)    .../<code>

此時還不行,因為這樣反射對象對應的是原數據對象的指針類型,必須要拿到當前類型的值類型(*v),如何做?Go提供了另外一個方法Elem()

<code>    ...    var x float64 = 3.4    v := reflect.ValueOf(&x)    p := v.Elem()    fmt.Println(p.CanSet()) // true        p.SetFloat(7.1)    fmt.Println(x) // 7.1/<code>

看以上代碼,就可以修改原數據了。

反射原理

不難發現,go的反射和interface在結構上是如此的相近!都分為兩部分:一部分是Type一部分是value。

反射會不會是比著interface來實現的?

反射是什麼意思?反射的意思是在運行時,能夠動態知道給定數據對象的類型和結構,並有機會修改它!現在一個數據對象,如何判斷它是什麼結構?數據interface中保存有結構數據呀,只要想辦法拿到該數據對應的內存地址,然後把該數據轉成interface,通過查看interface中的類型結構,就可以知道該數據的結構了呀~其實以上就是Go反射通俗的原理。

圖可以展示為:


圖解go反射實現原理


圖中結構中牽扯到的指針,都是unsafe.Pointer指針,具體這是個什麼指針,後續的文章中會有介紹,在此,你就姑且認為是可以指向Go系統中任意數據的指針就可以。

源碼部分 (以下部分可以忽略,是我在查閱代碼時候遇到的一點點坑。)

我們來看看具體的源碼:源碼在”GO SDK/src/refelct“包中,具體主要是包中的"type.go"和"value.go"這兩個文件。

可以簡單的認為,反射的核心代碼,主要是reflect.ValueOf()和reflect.TypeOf()這兩個函數。

先看類型轉換:reflect.TypeOf()

<code>// TypeOf returns the reflection Type that represents the dynamic type of i.// If i is a nil interface value, TypeOf returns nil.func TypeOf(i interface{}) Type {    eface := *(*emptyInterface)(unsafe.Pointer(&i))    return toType(eface.typ)}/<code>

其中出現了兩種數據結構,一個是Type,一個是emptyInterface分別看看這兩者的代碼:emptyInterface在 ”GO SDK/src/reflect/value.go“文件中

<code>// emptyInterface is the header for an interface{} value.type emptyInterface struct {    typ  *rtype    word unsafe.Pointer}// nonEmptyInterface is the header for an interface value with methods.type nonEmptyInterface struct {    // see ../runtime/iface.go:/Itab    itab *struct {        ityp *rtype // static interface type        typ  *rtype // dynamic concrete type        hash uint32 // copy of typ.hash        _    [4]byte        fun  [100000]unsafe.Pointer // method table    }    word unsafe.Pointer}/<code>

仔細一看,是空接口和包含方法的interface的兩個結構體。且和eface和iface 內容字段一致!不是有eface和iface了嗎?這兩者有什麼不同??

經過查閱代碼,發現:

interface源碼(位於”Go SDK/src/runtime/runtime2.go“)中的 eface和 iface 會和 反射源碼(位於”GO SDK/src/reflect/value.go“)中的emptyInterface和nonEmptyInterface保持數據同步!

此外,還有interface源碼(位於”Go SDK/src/runtime/type.go“)中的_type會和 反射源碼(位於”GO SDK/src/reflect/type.go“)中的rtype也保持數據同步一致!


參考文獻:

Go 1.12.9 反射源碼:/src/reflect/ 包

Go 1.12.9 interface 源碼:/src/runtime/runtime2.go 以及其他

https://studygolang.com/articles/2157https://blog.golang.org/laws-of-reflection

https://blog.csdn.net/u011957758/article/details/81193806https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/#431-

https://mp.weixin.qq.com/s/Hke0mSCEa4ga_GS_LUp78A


分享到:


相關文章: