手摸手Go 接口與反射

Go是強(qiáng)類型/靜態(tài)類型語(yǔ)言,每個(gè)變量在編譯時(shí)就已經(jīng)確定是哪種靜態(tài)類型。反射(reflection)是程序在運(yùn)行時(shí)可以訪問、檢測(cè)、修改自身狀態(tài)或行為的一種能力。在Java出現(xiàn)后迅速流行起來的概念,Go也提供了這種在運(yùn)行時(shí)更新、檢查變量值、調(diào)用變量的方法和變量支持的內(nèi)在操作的機(jī)制,一定程度上彌補(bǔ)了靜態(tài)語(yǔ)言在動(dòng)態(tài)行為上的不足。

正常來講,程序在編譯時(shí)會(huì)將變量轉(zhuǎn)換為內(nèi)存地址,變量名不會(huì)被編譯器寫入可執(zhí)行部分,那么運(yùn)行時(shí)程序就無法獲取自身的信息。支持反射的語(yǔ)言則需要在程序編譯期將變量的反射信息,如字段名、類型信息、結(jié)構(gòu)體信息等整合到可執(zhí)行文件中,并給程序提供接口訪問反射信息。這樣程序運(yùn)行時(shí)即可獲取類型的反射信息,并有能力操作修改它。

反射是把雙刃劍,雖然代碼更加靈活了但是

  • 代碼閱讀起來也困難了
  • 一定程度上破壞了靜態(tài)類型語(yǔ)言的編譯期檢查 運(yùn)行時(shí)會(huì)有panic風(fēng)險(xiǎn)
  • 降低了系統(tǒng)性能

我們?yōu)槭裁葱枰瓷洌?/h2>
  1. 無法預(yù)定義參數(shù)類型
  2. 函數(shù)需要根據(jù)入?yún)韯?dòng)態(tài)執(zhí)行

需要注意的是:Go中只有接口類型才可以反射,而反射又是建立在類型系統(tǒng)之上,so我們先來復(fù)習(xí)下類型與接口的知識(shí)

類型

Go是靜態(tài)類型語(yǔ)言。每個(gè)變量都有一個(gè)靜態(tài)類型,編譯時(shí)就已經(jīng)確定的類型:int、float32、*MyType、[]byte等等

type MyInt int

var i int
var j MyInt

上面的栗子中,i與j具有不同的靜態(tài)類型(i是int類型,j為MyInt類型),盡管他們的基礎(chǔ)類型都是int,但是他們之間不經(jīng)過轉(zhuǎn)換無法相互賦值。

類型的一個(gè)重要類別是接口類型,接口可以存儲(chǔ)任何非接口的具體值,只要該值實(shí)現(xiàn)了接口方法即可。

接口

接口是多個(gè)方法聲明的集合,側(cè)重于做什么,不關(guān)系怎么做 誰(shuí)來做。它更像是一種調(diào)用契約或協(xié)議(protocol)。接口解除了類型依賴,屏蔽了方法實(shí)現(xiàn)細(xì)節(jié),但接口的實(shí)現(xiàn)機(jī)制存在運(yùn)行時(shí)開銷。

Go的接口機(jī)制比較簡(jiǎn)潔,不像Java需要顯示聲明實(shí)現(xiàn)的接口,Go只要目標(biāo)類型方法集中包含了接口聲明的全部方法,就被稱為實(shí)現(xiàn)了該接口,無須顯示聲明。

如果一個(gè)接口沒有聲明任何方法,那么就是一個(gè)空接口interface{},類似JavaObject對(duì)象可以被賦值為任意類型的對(duì)象。但

Go語(yǔ)言的接口類型不是任意類型 只是任意類型可以通過類型轉(zhuǎn)換成接口變量

接下來我們來看看接口的數(shù)據(jù)結(jié)構(gòu),總結(jié)起來接口結(jié)構(gòu)如下:

interface structure

具體可以細(xì)分為

  • 不包含任何方法的接口interface{}
  • 包含一組方法的接口

Go語(yǔ)言使用runtime.eface表示不包含任何方法的接口,runtime.iface表示包含一組方法的接口。

  1. 不包含任何方法的接口
type eface struct {
    _type *_type
    data  unsafe.Pointer
}
eface
  1. 包含一組方法的接口
type iface struct {
    tab  *itab
    data unsafe.Pointer
}
type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
iface

可以看到不論空eface還是非空iface都包含了_type數(shù)據(jù)類型

type _type struct {
   size       uintptr //類型大小
   ptrdata    uintptr // 含有所有指針類型前綴大小
   hash       uint32 //類型hash值 避免在哈希表中計(jì)算
   tflag      tflag //額外類型信息標(biāo)志
   align      uint8 // 類型變量對(duì)齊方式
   fieldalign uint8 // 類型結(jié)構(gòu)字段對(duì)齊方式
   kind       uint8 // 類型種類
  alg        *typeAlg //存儲(chǔ)hash和equal兩個(gè)操作 map的key就是適用key的_type.alg.hash(k)獲取的hash值
   // gcdata stores the GC type data for the garbage collector.
   // If the KindGCProg bit is set in kind, gcdata is a GC program.
   // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
   gcdata    *byte
   str       nameOff //類型名字的偏移
   ptrToThis typeOff
}

當(dāng)然不同類型需要的描述是不一樣的,大多是利用_type組合其他基礎(chǔ)類型而成

接下來我們通過一個(gè)栗子拆解下接口內(nèi)存中的結(jié)構(gòu)究竟如何

type Animal interface {
    Say()
}

type Dog struct {
}

func (d *Dog) Say() {
    fmt.Println("wang wang")
}

//1
var animal Animal
dog := &Dog{}
//2
animal=dog
//3
var e interface{}
e = dog
  1. 初始化定義一個(gè)接口變量var animal Animal
iface init
  1. 將實(shí)現(xiàn)接口的對(duì)象賦值給接口變量animal=dog
iface full
  1. 定義一個(gè)空接口變量var e interface{}
empty
  1. 將實(shí)現(xiàn)接口的對(duì)象賦值給空接口變量e = dog
empty interface

至此,想必你應(yīng)該了解了接口的數(shù)據(jù)結(jié)構(gòu)及工作機(jī)制,接下來我們看看反射是如何工作的

反射

反射三大定律

1. Reflection goes from interface value to reflection object 接口數(shù)據(jù)-->反射對(duì)象

簡(jiǎn)單來說,反射是一種檢查存儲(chǔ)在接口變量中的類型和值的機(jī)制,reflect包定義了這兩個(gè)重要的類型TypeValue,任意接口值在反射中都可以理解為由 reflect.Typereflect.Value兩部分組成,可以通過reflect.TypeOf()reflect.ValueOf()函數(shù)來獲取任意對(duì)象的TypeValue

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
reflectTypeValue

舉個(gè)栗子

 var x float64 = 3.4
 fmt.Println("type:", reflect.TypeOf(x))

你可能會(huì)迷惑,你不是說接口變量才支持反射的嗎?別著急 我們來仔細(xì)看看reflect.TypeOfreflect.ValueOf是如何實(shí)現(xiàn)的

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

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

很簡(jiǎn)單,當(dāng)我們調(diào)用reflect.TypeOf(x)時(shí),x已經(jīng)存儲(chǔ)進(jìn)了一個(gè)空接口變量,reflect.TypeOf然后拆箱空接口變量獲取類型信息。

reflect.TypeOfreflect.ValueOf提供了大量的方法可以讓我們檢查和操作它們。

type Type interface {
    // 從內(nèi)存中申請(qǐng)一個(gè)類型值時(shí)對(duì)齊的字節(jié)數(shù).
    Align() int
    // 此類型作為結(jié)構(gòu)體字段時(shí)對(duì)齊的字節(jié)數(shù)
    FieldAlign() int
  //獲取類型的指定函數(shù)信息
    Method(int) Method
 //通過方法名獲取方法信息
    MethodByName(string) (Method, bool)
    //該類型可導(dǎo)出方法數(shù)量
    NumMethod() int
  // 返回包中定義類型的名稱 為定義類型返回空字符串
    Name() string
    // 返回類型的包路徑即唯一標(biāo)識(shí)包的路徑 如“encoding/base64”
  // 預(yù)定義類型、未定義類型、[]int返回空字符串
    PkgPath() string
  // 返回存儲(chǔ)該類型值需要的字節(jié)數(shù) 類似unsafe.Sizeof
    Size() uintptr
  // 返回該類型的字符串表示形式。
    String() string
    // 返回類型的特定種類
    Kind() Kind
    // 判斷該類型是否實(shí)現(xiàn)了u類型的接口
    Implements(u Type) bool
    // 判斷該類型是否可賦值給u類型
    AssignableTo(u Type) bool
    // 判斷該類型是否可轉(zhuǎn)換為u類型
    ConvertibleTo(u Type) bool
    // 判斷該類型的值是否可比較
    Comparable() bool
    // 方法僅適用于某些類型
    // 取決于具體類型
    //  Int*, Uint*, Float*, Complex*: Bits
    //  Array: Elem, Len
    //  Chan: ChanDir, Elem
    //  Func: In, NumIn, Out, NumOut, IsVariadic.
    //  Map: Key, Elem
    //  Ptr: Elem
    //  Slice: Elem
    //  Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

    // 返回類型占用的bit值
    //非 sized or unsized Int, Uint, Float, or Complex 會(huì)panic
    Bits() int
    // 返回channel類型 非chan類型panic
    ChanDir() ChanDir
  // 判斷函數(shù)是否有可變參數(shù) 非函數(shù)類型會(huì)panic
    IsVariadic() bool
    // 返回元素類型
    // 非 Array, Chan, Map, Ptr, or Slice會(huì)panic
    Elem() Type
    // It panics if the type's Kind is not Struct.
    // It panics if i is not in the range [0, NumField()).
  // 返回結(jié)構(gòu)體類型第i個(gè)字段
    Field(i int) StructField
    // 等價(jià)于Field(i)
    // It panics if the type's Kind is not Struct.
    FieldByIndex(index []int) StructField
    // 根據(jù)名字返回字段信息
    // and a boolean indicating if the field was found.
    FieldByName(name string) (StructField, bool)
  //利用函數(shù)查找字段名符合條件的字段信息 使用廣度優(yōu)先的策略 如果發(fā)現(xiàn)多個(gè)匹配則不返回匹配項(xiàng)
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // It panics if the type's Kind is not Func.
    // It panics if i is not in the range [0, NumIn()).
  // 返回函數(shù)第i個(gè)入?yún)?    In(i int) Type
    // It panics if the type's Kind is not Map.
  // 返回map的key類型
    Key() Type
    // It panics if the type's Kind is not Array.
  // 返回?cái)?shù)組類型的長(zhǎng)度
    Len() int
    // It panics if the type's Kind is not Struct.
  // 返回結(jié)構(gòu)體類型字段數(shù)量
    NumField() int
    // It panics if the type's Kind is not Func.
  // 返回函數(shù)類型入?yún)?shù)量
    NumIn() int
    // It panics if the type's Kind is not Func.
  // 返回函數(shù)類型出參數(shù)量
    NumOut() int
    // It panics if the type's Kind is not Func.
    // It panics if i is not in the range [0, NumOut()).
  // 返回函數(shù)類型第i個(gè)出參
    Out(i int) Type

    common() *rtype
    uncommon() *uncommonType
}

Value

type Value struct {
    // typ 包含由Value表示值的類型
    typ *rtype
    // 指針值數(shù)據(jù),如果設(shè)置flagIndir則指向數(shù)據(jù)
    // 當(dāng)設(shè)置flagIndir或typ.pointers()為true時(shí)有效
    ptr unsafe.Pointer

    // flag保存有關(guān)值的元數(shù)據(jù)
    // 最低位是flag標(biāo)志位:
    //  - flagStickyRO: 通過未導(dǎo)出未嵌入的字段獲取 故只讀
    //  - flagEmbedRO: 通過未導(dǎo)出嵌入字段獲取故只讀
    //  - flagIndir: val保存指向數(shù)據(jù)的指針
    //  - flagAddr: v.CanAddr 為 true (表示 flagIndir)
    //  - flagMethod: v 為方法值
    // 接下來的5位給出值的類型
    // 重復(fù)typ.Kind() 方法值除外.
    // 剩余23+位給方法值的方法編號(hào)
    // 如果flag.kind() != Func, 代碼可假定未設(shè)置flagMethod
    // 如果ifaceIndir(typ), 代碼可假設(shè)設(shè)置了flagIndir
    flag
}

2. Reflection goes from reflection object to interface value 反射對(duì)象 -->接口數(shù)據(jù)

像物理反射一樣,Go的反射也會(huì)生成自己的逆。給出一個(gè)reflect.Value我們可以使用Interface()方法獲取接口的值。實(shí)際上就是將該類型和值信息打包成接口表示形式并返回。

// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

例如:

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
reflect3

當(dāng)然reflect.Value通過Value.Type()也可以直接獲取reflect.Type

reflect2

3. To modify a reflection object,the value must be settable 若數(shù)據(jù)可修改 可通過反射對(duì)象來修改它

我們先來看個(gè)栗子:

    var a float64
    fmt.Println(a)
    va := reflect.ValueOf(a)
  va.SetFloat(11) 
    fmt.Println(a)

輸出:

panic: using unaddressable value

為何?看似操作沒問題。其實(shí)仔細(xì)想想,Go是值傳遞va := reflect.ValueOf(a)中我們相當(dāng)于傳遞了a的拷貝給了reflect.ValueOf,因此即使va.SetFloat(11)修改成功了也無法到達(dá)修改a原始值的目的,故而利用這種Type是否CanSet來避免這種問題。正確做法

  • 首先根據(jù)變量地址獲取reflect.Valueva := reflect.ValueOf(&a)
  • va.SetFloat(11)此時(shí)依然無法成功 因?yàn)榇藭r(shí)的va仍然是一個(gè)拷貝值,如若修改需要使用va.Elem()獲取*va
    var a float64
    fmt.Println(a)
    va := reflect.ValueOf(&a)
  va.Elem().SetFloat(11) 
    fmt.Println(a) // 11

反射的應(yīng)用

反射廣泛應(yīng)用在對(duì)象序列化,fmt相關(guān)的函數(shù)以及ORM(Object Relational Mapping)等等

例如:JSON序列化

Go內(nèi)置的Json序列化提供了兩個(gè)方法

func Marshal(v interface{}) ([]byte, error) 
func Unmarshal(data []byte, v interface{}) error 

序列化和反序列化參數(shù)中都有interface{}類型的變量,所以當(dāng)我們調(diào)用這個(gè)函數(shù)時(shí)需要使用reflect包中的方法后期參數(shù)的reflect.Valuereflect.Type,進(jìn)而調(diào)用其get、set方法。

序列化

func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
    ......

    switch t.Kind() {
    case reflect.Bool:
        return boolEncoder
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return intEncoder
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return uintEncoder
    case reflect.Float32:
        return float32Encoder
    case reflect.Float64:
        return float64Encoder
    case reflect.String:
        return stringEncoder
    case reflect.Interface:
        return interfaceEncoder
    case reflect.Struct:
        return newStructEncoder(t)
    case reflect.Map:
        return newMapEncoder(t)
    case reflect.Slice:
        return newSliceEncoder(t)
    case reflect.Array:
        return newArrayEncoder(t)
    case reflect.Ptr:
        return newPtrEncoder(t)
    default:
        return unsupportedTypeEncoder
    }
}
reflect json

總結(jié)

Go作為靜態(tài)語(yǔ)言,相對(duì)于動(dòng)態(tài)語(yǔ)言,在靈活性上受到某些限制。但是通過reflect包提供類似動(dòng)態(tài)語(yǔ)言的功能,你可以運(yùn)行時(shí)獲取參數(shù)的ValueType進(jìn)而完成一些特定的需求。其轉(zhuǎn)換關(guān)系如圖

reflect4
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 各位學(xué)習(xí)Go語(yǔ)言的朋友,周末好,這次跟大家聊一聊Go語(yǔ)言的一個(gè)高級(jí)話題:反射。 這篇文章是從我過去的學(xué)習(xí)筆記修改來...
    大彬_一起學(xué)Golang閱讀 1,422評(píng)論 2 21
  • 本文轉(zhuǎn)載自https://github.com/KeKe-Li/For-learning-Go-Tutorial/...
    雪域迷影閱讀 449評(píng)論 0 0
  • 第一次知道反射的時(shí)候還是許多年前在學(xué)校里玩 C# 的時(shí)候。那時(shí)總是弄不清楚這個(gè)復(fù)雜的玩意能有什么實(shí)際用途……然后發(fā)...
    勿以浮沙筑高臺(tái)閱讀 1,190評(píng)論 0 9
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,833評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽閱讀 10,885評(píng)論 0 11

友情鏈接更多精彩內(nèi)容