go interface转int_24. 图解 Go语言中的反射三定律
大家好,我是明哥。
由于某些不可抗力,之前該專欄的所有內(nèi)容都被刪除了。因此之后的一段時間內(nèi),我會重新發(fā)布這些文章,先給已經(jīng)看過的關注者提個醒。
本文原文:http://golang.iswbm.com
Github:http://github.com/iswbm/GolangCodingTime
很多人都知道我是從 Python 轉(zhuǎn)過來學習 Go 語言的,當我在使用 Python 的時候,我甚至可以做到不需要知道什么是內(nèi)省,什么是反射,就可以立即使用內(nèi)省去做一些事情。
而在學習 Go 語言后,反射在我這卻變成了一個難點,一直感覺這個 反射對象 的概念異常的抽象。
這篇文章還是會跟上篇文章一樣,盡量使用圖解來解釋一些抽象的概念,如果是我理解有誤,還希望你在文章尾部給我留言指正,謝謝。
關于反射的內(nèi)容,我分為了好幾篇,這一篇是入門篇,會從經(jīng)典的反射三大定律入手,寫一些 demo 代碼,告訴你反射的基本內(nèi)容。
1. 真實世界與反射世界
在本篇文章里,為了區(qū)分反射前后的變量值類型,我將反射前環(huán)境稱為 真實世界,而將反射后的環(huán)境稱為 反射世界。這種比喻不嚴謹,但是對于我理解是有幫助的,也希望對你有用。
在反射的世界里,我們擁有了獲取一個對象的類型,屬性及方法的能力。
2. 兩種類型:Type 和 Value
在 Go 反射的世界里,有兩種類型非常重要,是整個反射的核心,在學習 reflect 包的使用時,先得學習下這兩種類型:
它們分別對應著真實世界里的 type 和 value,只不過在反射對象里,它們擁有更多的內(nèi)容。
從源碼上來看,reflect.Type 是以一個接口的形式存在的
type Type interface {Align() intFieldAlign() intMethod(int) MethodMethodByName(string) (Method, bool)NumMethod() intName() stringPkgPath() stringSize() uintptrString() stringKind() KindImplements(u Type) boolAssignableTo(u Type) boolConvertibleTo(u Type) boolComparable() boolBits() intChanDir() ChanDirIsVariadic() boolElem() TypeField(i int) StructFieldFieldByIndex(index []int) StructFieldFieldByName(name string) (StructField, bool)FieldByNameFunc(match func(string) bool) (StructField, bool)In(i int) TypeKey() TypeLen() intNumField() intNumIn() intNumOut() intOut(i int) Typecommon() *rtypeuncommon() *uncommonType }而 reflect.Value 是以一個結構體的形式存在,
type Value struct {typ *rtypeptr unsafe.Pointerflag }同時它接收了很多的方法(見下表),這里出于篇幅的限制這里也沒辦法一一介紹。
Addr Bool Bytes runes CanAddr CanSet Call CallSlice call Cap Close Complex Elem Field FieldByIndex FieldByName FieldByNameFunc Float Index Int CanInterface Interface InterfaceData IsNil IsValid IsZero Kind Len MapIndex MapKeys MapRange Method NumMethod MethodByName NumField OverflowComplex OverflowFloat OverflowInt OverflowUint Pointer Recv recv Send send Set SetBool SetBytes setRunes SetComplex SetFloat SetInt SetLen SetCap SetMapIndex SetUint SetPointer SetString Slice Slice3 String TryRecv TrySend Type Uint UnsafeAddr assignTo Convert通過上一節(jié)的內(nèi)容(關于接口的三個 『潛規(guī)則』),我們知道了一個接口變量,實際上都是由一 pair 對(type 和 data)組合而成,pair 對中記錄著實際變量的值和類型。也就是說在真實世界里,type 和 value 是合并在一起組成 接口變量的。
而在反射的世界里,type 和 data 卻是分開的,他們分別由 reflect.Type 和 reflect.Value 來表現(xiàn)。
3. 解讀反射的三大定律
Go 語言里有個反射三定律,是你在學習反射時,很重要的參考:
翻譯一下,就是:
第一定律
Reflection goes from interface value to reflection object.為了實現(xiàn)從接口變量到反射對象的轉(zhuǎn)換,需要提到 reflect 包里很重要的兩個方法:
這兩個方法返回的對象,我們稱之為反射對象:Type object 和 Value object。
舉個例子,看下這兩個方法是如何使用的?
package mainimport ( "fmt" "reflect" )func main() {var age interface{} = 25fmt.Printf("原始接口變量的類型為 %T,值為 %v n", age, age)t := reflect.TypeOf(age)v := reflect.ValueOf(age)// 從接口變量到反射對象fmt.Printf("從接口變量到反射對象:Type對象的類型為 %T n", t)fmt.Printf("從接口變量到反射對象:Value對象的類型為 %T n", v)}輸出如下
原始接口變量的類型為 int,值為 25 從接口變量到反射對象:Type對象的類型為 *reflect.rtype 從接口變量到反射對象:Value對象的類型為 reflect.Value如此我們完成了從接口類型變量到反射對象的轉(zhuǎn)換。
等等,上面我們定義的 age 不是 int 類型的嗎?第一法則里怎么會說是接口類型的呢?
關于這點,其實在上一節(jié)(關于接口的三個 『潛規(guī)則』)已經(jīng)提到過了,由于 TypeOf 和 ValueOf 兩個函數(shù)接收的是 interface{} 空接口類型,而 Go 語言函數(shù)都是值傳遞,因此Go語言會將我們的類型隱式地轉(zhuǎn)換成接口類型。
// TypeOf returns the reflection Type of the value in the interface{}.TypeOf returns nil. func TypeOf(i interface{}) Type// ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value. func ValueOf(i interface{}) Value第二定律
Reflection goes from reflection object to interface value.和第一定律剛好相反,第二定律描述的是,從反射對象到接口變量的轉(zhuǎn)換。
通過源碼可知, reflect.Value 的結構體會接收 Interface 方法,返回了一個 interface{} 類型的變量(注意:只有 Value 才能逆向轉(zhuǎn)換,而 Type 則不行,這也很容易理解,如果 Type 能逆向,那么逆向成什么呢?)
// Interface returns v's current value as an interface{}. // It is equivalent to: // var i interface{} = (v's underlying value) // It panics if the Value was obtained by accessing // unexported struct fields. func (v Value) Interface() (i interface{}) {return valueInterface(v, true) }這個函數(shù)就是我們用來實現(xiàn)將反射對象轉(zhuǎn)換成接口變量的一個橋梁。
例子如下
package mainimport ( "fmt" "reflect" )func main() {var age interface{} = 25fmt.Printf("原始接口變量的類型為 %T,值為 %v n", age, age)t := reflect.TypeOf(age)v := reflect.ValueOf(age)// 從接口變量到反射對象fmt.Printf("從接口變量到反射對象:Type對象的類型為 %T n", t)fmt.Printf("從接口變量到反射對象:Value對象的類型為 %T n", v)// 從反射對象到接口變量i := v.Interface()fmt.Printf("從反射對象到接口變量:新對象的類型為 %T 值為 %v n", i, i)}輸出如下
原始接口變量的類型為 int,值為 25 從接口變量到反射對象:Type對象的類型為 *reflect.rtype 從接口變量到反射對象:Value對象的類型為 reflect.Value 從反射對象到接口變量:新對象的類型為 int 值為 25當然了,最后轉(zhuǎn)換后的對象,靜態(tài)類型為 interface{} ,如果要轉(zhuǎn)成最初的原始類型,需要再類型斷言轉(zhuǎn)換一下,關于這點,我已經(jīng)在上一節(jié)里講解過了,你可以點此前往復習:(關于接口的三個 『潛規(guī)則』)。
i := v.Interface().(int)至此,我們已經(jīng)學習了反射的兩大定律,對這兩個定律的理解,我畫了一張圖,你可以用下面這張圖來加強理解,方便記憶。
第三定律
To modify a reflection object, the value must be settable.反射世界是真實世界的一個『映射』,是我的一個描述,但這并不嚴格,因為并不是你在反射世界里所做的事情都會還原到真實世界里。
第三定律引出了一個 settable (可設置性,或可寫性)的概念。
其實早在以前的文章中,我們就一直在說,Go 語言里的函數(shù)都是值傳遞,只要你傳遞的不是變量的指針,你在函數(shù)內(nèi)部對變量的修改是不會影響到原始的變量的。
回到反射上來,當你使用 reflect.Typeof 和 reflect.Valueof 的時候,如果傳遞的不是接口變量的指針,反射世界里的變量值始終將只是真實世界里的一個拷貝,你對該反射對象進行修改,并不能反映到真實世界里。
因此在反射的規(guī)則里
- 不是接收變量指針創(chuàng)建的反射對象,是不具備『可寫性』的
- 是否具備『可寫性』,可使用 CanSet() 來獲取得知
- 對不具備『可寫性』的對象進行修改,是沒有意義的,也認為是不合法的,因此會報錯。
輸出如下
可寫性為: false要讓反射對象具備可寫性,需要注意兩點
完整代碼如下
package mainimport ("fmt""reflect" )func main() {var name string = "Go編程時光"v1 := reflect.ValueOf(&name)fmt.Println("v1 可寫性為:", v1.CanSet())v2 := v1.Elem()fmt.Println("v2 可寫性為:", v2.CanSet()) }輸出如下
v1 可寫性為: false v2 可寫性為: true知道了如何使反射的世界里的對象具有可寫性后,接下來是時候了解一下如何對修改更新它。
反射對象,都會有如下幾個以 Set 單詞開頭的方法
這些方法就是我們修改值的入口。
來舉個例子
package mainimport ("fmt""reflect" )func main() {var name string = "Go編程時光"fmt.Println("真實世界里 name 的原始值為:", name)v1 := reflect.ValueOf(&name)v2 := v1.Elem()v2.SetString("Python編程時光")fmt.Println("通過反射對象進行更新后,真實世界里 name 變?yōu)?#xff1a;", name) }輸出如下
真實世界里 name 的原始值為: Go編程時光 通過反射對象進行更新后,真實世界里 name 變?yōu)?#xff1a; Python編程時光參考文章
- 思否:Go 語言反射三定律
好了,今天的文章就到這里了。
若今天的分享對你有幫助,不如點個贊,支持一下?
總結
以上是生活随笔為你收集整理的go interface转int_24. 图解 Go语言中的反射三定律的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: esp8266 阿里云 arduino_
- 下一篇: 如何改进yolov3_揭秘YOLOv3鲜