<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          解密 Go 語言之反射 reflect

          共 7149字,需瀏覽 15分鐘

           ·

          2021-01-15 18:36

          在所有的語言中,反射這一功能基本屬于必不可少的模塊。

          雖說 “反射” 這個(gè)詞讓人根深蒂固,但更多的還是 WHY。反射到底是什么,反射又是基于什么法則實(shí)現(xiàn)的?

          今天我們通過這篇文章來一一揭曉,以 Go 語言為例,了解反射到底為何物,其底層又是如何實(shí)現(xiàn)的。

          反射是什么

          在計(jì)算機(jī)學(xué)中,反射是指計(jì)算機(jī)程序在運(yùn)行時(shí)(runtime)可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力。

          用比喻來說,反射就是程序在運(yùn)行的時(shí)候能夠 “觀察” 并且修改自己的行為(來自維基百科)。

          簡單來講就是,應(yīng)用程序能夠在運(yùn)行時(shí)觀察到變量的值,并且能夠修改他。

          一個(gè)例子

          最常見的 reflect 標(biāo)準(zhǔn)庫例子,如下:

          import?(
          ?"fmt"
          ?"reflect"
          )

          func?main()?{
          ?rv?:=?[]interface{}{"hi",?42,?func()?{}}
          ?for?_,?v?:=?range?rv?{
          ??switch?v?:=?reflect.ValueOf(v);?v.Kind()?{
          ??case?reflect.String:
          ???fmt.Println(v.String())
          ??case?reflect.Int,?reflect.Int8,?reflect.Int16,?reflect.Int32,?reflect.Int64:
          ???fmt.Println(v.Int())
          ??default:
          ???fmt.Printf("unhandled?kind?%s",?v.Kind())
          ??}
          ?}
          }

          輸出結(jié)果:

          hi
          42
          unhandled?kind?func

          在程序中主要是聲明了 rv 變量,變量類型為 interface{},其包含 3 個(gè)不同類型的值,分別是字符串、數(shù)字、閉包。

          而在使用 interface{} 時(shí)常見于不知道入?yún)⒄呔唧w的基本類型是什么,那么我們就會(huì)用 interface{} 類型來做一個(gè)偽 “泛型”。

          此時(shí)又會(huì)引出一個(gè)新的問題,既然入?yún)⑹?interface{},那么出參時(shí)呢?

          Go 語言是強(qiáng)類型語言,入?yún)⑹?interface{},出參也肯定是跑不了的,因此必然離不開類型的判斷,這時(shí)候就要用到反射,也就是 reflect 標(biāo)準(zhǔn)庫。反射過后又再進(jìn)行 (type) 的類型斷言。

          這就是我們?cè)诰帉懗绦驎r(shí)最常遇見的一個(gè)反射使用場景。

          Go reflect

          reflect 標(biāo)準(zhǔn)庫中,最核心的莫過于 reflect.Typereflect.Value 類型。而在反射中所使用的方法都圍繞著這兩者進(jìn)行,其方法主要含義如下:

          • TypeOf 方法:用于提取入?yún)⒅档?strong>類型信息。

          • ValueOf 方法:用于提取存儲(chǔ)的變量的值信息。

          reflect.TypeOf

          演示程序:

          func?main()?{
          ?blog?:=?Blog{"煎魚"}
          ?typeof?:=?reflect.TypeOf(blog)
          ?fmt.Println(typeof.String())
          }

          輸出結(jié)果:

          main.Blog

          從輸出結(jié)果中,可得出 reflect.TypeOf 成功解析出 blog 變量的類型是 main.Blog,也就是連 package 都知道了。

          通過人識(shí)別的角度來看似乎很正常,但程序就不是這樣了。他是怎么知道 “他” 是哪個(gè) package 下的什么呢?

          我們一起追一下源碼看看:

          func?TypeOf(i?interface{})?Type?{
          ?eface?:=?*(*emptyInterface)(unsafe.Pointer(&i))
          ?return?toType(eface.typ)
          }

          從源碼層面來看,TypeOf 方法中主要涉及三塊操作,分別如下:

          1. 使用 unsafe.Pointer 方法獲取任意類型且可尋址的指針值。

          2. 利用 emptyInterface 類型進(jìn)行強(qiáng)制的 interface 類型轉(zhuǎn)換。

          3. 調(diào)用 toType 方法轉(zhuǎn)換為可供外部使用的 Type 類型。

          而這之中信息量最大的是 emptyInterface 結(jié)構(gòu)體中的 rtype 類型:

          type?rtype?struct?{
          ?size???????uintptr
          ?ptrdata????uintptr?
          ?hash???????uint32?
          ?tflag??????tflag?
          ?align??????uint8??
          ?fieldAlign?uint8??
          ?kind???????uint8???
          ?equal?????func(unsafe.Pointer,?unsafe.Pointer)?bool
          ?gcdata????*byte??
          ?str???????nameOff?
          ?ptrToThis?typeOff?
          }

          在使用上最重要的是 rtype 類型,其實(shí)現(xiàn)了 Type 類型的所有接口方法,因此他可以直接作為 Type 類型返回。

          Type 本質(zhì)上是一個(gè)接口實(shí)現(xiàn),其包含了獲取一個(gè)類型所必要的所有方法:

          type?Type?interface?{
          ?//?適用于所有類型
          ?//?返回該類型內(nèi)存對(duì)齊后所占用的字節(jié)數(shù)
          ?Align()?int

          ?//?僅作用于?strcut?類型
          ?//?返回該類型內(nèi)存對(duì)齊后所占用的字節(jié)數(shù)
          ?FieldAlign()?int

          ?//?返回該類型的方法集中的第?i?個(gè)方法
          ?Method(int)?Method

          ?//?根據(jù)方法名獲取對(duì)應(yīng)方法集中的方法
          ?MethodByName(string)?(Method,?bool)

          ?//?返回該類型的方法集中導(dǎo)出的方法的數(shù)量。
          ?NumMethod()?int

          ?//?返回該類型的名稱
          ?Name()?string
          ?...
          }

          建議大致過一遍,了解清楚有哪些方法,再針對(duì)向看就好。

          主體思想是給自己大腦建立一個(gè)索引,便于后續(xù)快速到 pkg.go.dev 上查詢即可。

          reflect.ValueOf

          演示程序:

          func?main()?{
          ?var?x?float64?=?3.4
          ?fmt.Println("value:",?reflect.ValueOf(x))
          }

          輸出結(jié)果:

          value:?3.4

          從輸出結(jié)果中,可得知通過 reflect.ValueOf 成功獲取到了變量 x 的值為 3.4。與 reflect.TypeOf 形成一個(gè)相匹配,一個(gè)負(fù)責(zé)獲取類型,一個(gè)負(fù)責(zé)獲取值。

          那么 reflect.ValueOf 是怎么獲取到值的呢,核心源碼如下:

          func?ValueOf(i?interface{})?Value?{
          ?if?i?==?nil?{
          ??return?Value{}
          ?}

          ?escapes(i)

          ?return?unpackEface(i)
          }

          func?unpackEface(i?interface{})?Value?{
          ?e?:=?(*emptyInterface)(unsafe.Pointer(&i))
          ?t?:=?e.typ
          ?if?t?==?nil?{
          ??return?Value{}
          ?}
          ?f?:=?flag(t.Kind())
          ?if?ifaceIndir(t)?{
          ??f?|=?flagIndir
          ?}
          ?return?Value{t,?e.word,?f}
          }

          從源碼層面來看,ValueOf 方法中主要涉及如下幾個(gè)操作:

          1. 調(diào)用 escapes 讓變量 i 逃逸到堆上。

          2. 將變量 i 強(qiáng)制轉(zhuǎn)換為 emptyInterface 類型。

          3. 將所需的信息(其中包含值的具體類型和指針)組裝成 reflect.Value 類型后返回。

          何時(shí)類型轉(zhuǎn)換

          在調(diào)用 reflect 進(jìn)行一系列反射行為時(shí),Go 又是在什么時(shí)候進(jìn)行的類型轉(zhuǎn)換呢?

          畢竟我們傳入的是 float64,而函數(shù)如參數(shù)是 inetrface 類型。

          查看匯編如下:

          $?go?tool?compile?-S?main.go?????????????????????????
          ?...
          ?0x0058?00088?($GOROOT/src/reflect/value.go:2817)?LEAQ?type.float64(SB),?CX
          ?0x005f?00095?($GOROOT/src/reflect/value.go:2817)?MOVQ?CX,?reflect.dummy+8(SB)
          ?0x0066?00102?($GOROOT/src/reflect/value.go:2817)?PCDATA?$0,?$-2
          ?0x0066?00102?($GOROOT/src/reflect/value.go:2817)?CMPL?runtime.writeBarrier(SB),?$0
          ?0x006d?00109?($GOROOT/src/reflect/value.go:2817)?JNE?357
          ?0x0073?00115?($GOROOT/src/reflect/value.go:2817)?MOVQ?AX,?reflect.dummy+16(SB)
          ?0x007a?00122?($GOROOT/src/reflect/value.go:2348)?PCDATA?$0,?$-1
          ?0x007a?00122?($GOROOT/src/reflect/value.go:2348)?MOVQ?CX,?reflect.i+64(SP)
          ?0x007f?00127?($GOROOT/src/reflect/value.go:2348)?MOVQ?AX,?reflect.i+72(SP)
          ?...

          顯然,Go 語言會(huì)在編譯階段就會(huì)完成分析,且進(jìn)行類型轉(zhuǎn)換。這樣子 reflect 真正所使用的就是 interface 類型了。

          reflect.Set

          演示程序:

          func?main()?{
          ?i?:=?2.33
          ?v?:=?reflect.ValueOf(&i)
          ?v.Elem().SetFloat(6.66)
          ?log.Println("value:?",?i)
          }

          輸出結(jié)果:

          value:??6.66

          從輸出結(jié)果中,我們可得知在調(diào)用 reflect.ValueOf 方法后,我們利用 SetFloat 方法進(jìn)行了值變更。

          核心的方法之一就是 Setter 相關(guān)的方法,我們可以一起看看其源碼是怎么實(shí)現(xiàn)的:

          func?(v?Value)?Set(x?Value)?{
          ?v.mustBeAssignable()
          ?x.mustBeExported()?//?do?not?let?unexported?x?leak
          ?var?target?unsafe.Pointer
          ?if?v.kind()?==?Interface?{
          ??target?=?v.ptr
          ?}
          ?x?=?x.assignTo("reflect.Set",?v.typ,?target)
          ?if?x.flag&flagIndir?!=?0?{
          ??typedmemmove(v.typ,?v.ptr,?x.ptr)
          ?}?else?{
          ??*(*unsafe.Pointer)(v.ptr)?=?x.ptr
          ?}
          }
          1. 檢查反射對(duì)象及其字段是否可以被設(shè)置。

          2. 檢查反射對(duì)象及其字段是否導(dǎo)出(對(duì)外公開)。

          3. 調(diào)用 assignTo 方法創(chuàng)建一個(gè)新的反射對(duì)象并對(duì)原本的反射對(duì)象進(jìn)行覆蓋。

          4. 根據(jù) assignTo 方法所返回的指針值,對(duì)當(dāng)前反射對(duì)象的指針進(jìn)行值的修改。

          簡單來講就是,檢查是否可以設(shè)置,接著創(chuàng)建一個(gè)新的對(duì)象,最后對(duì)其修改。是一個(gè)非常標(biāo)準(zhǔn)的賦值流程。

          反射三大定律

          Go 語言中的反射,其歸根究底都是在實(shí)現(xiàn)三大定律:

          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.

          我們將針對(duì)這核心的三大定律進(jìn)行介紹和說明,以此來理解 Go 反射里的各種方法是基于什么理念實(shí)現(xiàn)的。

          第一定律

          反射的第一定律是:“反射可以從接口值(interface)得到反射對(duì)象”。

          示例代碼:

          func?main()?{
          ?var?x?float64?=?3.4
          ?fmt.Println("type:",?reflect.TypeOf(x))
          }

          輸出結(jié)果:

          type:?float64

          可能有讀者就迷糊了,我明明在代碼中傳入的變量 x,他的類型是 float64。怎么就成從接口值得到反射對(duì)象了。

          其實(shí)不然,雖然在代碼中我們所傳入的變量基本類型是 float64,但是 reflect.TypeOf 方法入?yún)⑹?interface{},本質(zhì)上 Go 語言內(nèi)部對(duì)其是做了類型轉(zhuǎn)換的。這一塊會(huì)在后面會(huì)進(jìn)一步展開說明。

          第二定律

          反射的第二定律是:“可以從反射對(duì)象得到接口值(interface)”。其與第一條定律是相反的定律,可以是互相補(bǔ)充了。

          示例代碼:

          func?main()?{
          ?vo?:=?reflect.ValueOf(3.4)
          ?vf?:=?vo.Interface().(float64)
          ?log.Println("value:",?vf)
          }

          輸出結(jié)果:

          value:?3.4

          可以看到在示例代碼中,變量 vo 已經(jīng)是反射對(duì)象,然后我們可以利用其所提供的的 Interface 方法獲取到接口值(interface),并最后強(qiáng)制轉(zhuǎn)換回我們?cè)嫉淖兞款愋汀?/p>

          第三定律

          反射的第三定律是:“要修改反射對(duì)象,該值必須可以修改”。第三條定律看上去與第一、第二條均無直接關(guān)聯(lián),但卻是必不可少的,因?yàn)榉瓷湓诠こ虒?shí)踐中,目的一就是可以獲取到值和類型,其二就是要能夠修改他的值。

          否則反射出來只能看,不能動(dòng),就會(huì)造成這個(gè)反射很雞肋。例如:應(yīng)用程序中的配置熱更新,必然會(huì)涉及配置項(xiàng)相關(guān)的變量變動(dòng),大多會(huì)使用到反射來變動(dòng)初始值。

          示例代碼:

          func?main()?{
          ?i?:=?2.33
          ?v?:=?reflect.ValueOf(&i)
          ?v.Elem().SetFloat(6.66)
          ?log.Println("value:?",?i)
          }

          輸出結(jié)果:

          value:??6.66

          單從結(jié)果來看,變量 i 的值確實(shí)從 2.33 變成了 6.66,似乎非常完美。

          但是單看代碼,似乎有些 “問題”,怎么設(shè)置一個(gè)反射值這么 ”麻煩“:

          1. 為什么必須傳入變量 i 的指針引用?

          2. 為什么變量 v 在設(shè)置前還需要 Elem 一下?

          本叛逆的 Gophper 表示我就不這么設(shè)置,行不行呢,會(huì)不會(huì)出現(xiàn)什么問題:

          func?main()?{
          ?i?:=?2.33
          ?reflect.ValueOf(i).SetFloat(6.66)
          ?log.Println("value:?",?i)
          }

          報(bào)錯(cuò)信息:

          panic:?reflect:?reflect.Value.SetFloat?using?unaddressable?value

          goroutine?1?[running]:
          reflect.flag.mustBeAssignableSlow(0x8e)
          ????????/usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:259?+0x138
          reflect.flag.mustBeAssignable(...)
          ????????/usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:246
          reflect.Value.SetFloat(0x10b2980,?0xc00001a0b0,?0x8e,?0x401aa3d70a3d70a4)
          ????????/usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:1609?+0x37
          main.main()
          ????????/Users/eddycjy/go-application/awesomeProject/main.go:10?+0xc5

          根據(jù)上述提示可知,由于使用 “使用不可尋址的值”,因此示例程序無法正常的運(yùn)作下去。并且這是一個(gè) reflect 標(biāo)準(zhǔn)庫本身就加以防范了的硬性要求。

          這么做的原因在于,Go 語言的函數(shù)調(diào)用的傳遞都是值拷貝的,因此若不傳指針引用,單純值傳遞,那么肯定是無法變動(dòng)反射對(duì)象的源值的。因此 Go 標(biāo)準(zhǔn)庫就對(duì)其進(jìn)行了邏輯判斷,避免出現(xiàn)問題。

          因此期望變更反射對(duì)象的源值時(shí),我們必須主動(dòng)傳入對(duì)應(yīng)變量的指針引用,并且調(diào)用 reflect 標(biāo)準(zhǔn)庫的 Elem 方法來獲取指針?biāo)赶虻脑醋兞浚⑶易詈笳{(diào)用 Set 相關(guān)方法來進(jìn)行設(shè)置。

          總結(jié)

          通過本文我們學(xué)習(xí)并了解了 Go 反射是如何使用,又是基于什么定律設(shè)計(jì)的。另外我們稍加關(guān)注,不難發(fā)現(xiàn) Go 的反射都是基于接口(interface)來實(shí)現(xiàn)的,更進(jìn)一步來講,Go 語言中運(yùn)行時(shí)的功能很多都是基于接口來實(shí)現(xiàn)的。

          整體來講,Go 反射是圍繞著三者進(jìn)行的,分別是 Type、Value 以及 Interface,三者相輔相成,而反射本質(zhì)上與 Interface 存在直接關(guān)系,Interface 這一塊的內(nèi)容我們也將在后續(xù)的文章進(jìn)行進(jìn)一步的剖析。


          喜歡本教程系列的同學(xué)
          歡迎長按下圖訂閱!

          ???


          瀏覽 21
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩电影在线视频 | 欧美精品卡一操逼 | 中文无码视频直接看 | 激情乱伦无码 | 一区综合网 |