Go 反射與interface拾遺

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Go能實現介面的動態性和反射的基礎是:編譯期為運行時提供了類型資訊。interface底層有兩種結構,上一節講了帶方法的iface,這一節補充不帶方法的eface結構。

interface之eface

Go中的任何對象都可以表示為interface{}。它扮演的角色與C中的void*差不多,區別在於interface{}中包含有類型資訊,可以實現反射。

eface資料結構描述gcdata域用於記憶體回收,size描述類型的大小,hash表示資料的hash值,align是對齊,fieldalign是這個資料嵌入結構體時的對齊,kind是枚舉值。alg是函數指標的數組,儲存了hash/equal/print/copy四個函數操作。uncommentType指向這個類型的方法集。

type eface struct {_type *_typedata unsafe.Pointer}type _type struct {size       uintptrptrdata    uintptr // size of memory prefix holding all pointershash       uint32_unused    uint8align      uint8fieldalign uint8kind       uint8alg        *typeAlggcdata  *byte_string *stringx       *uncommontypeptrto   *_typezero    *byte // ptr to the zero value for this type}

運行時convT2E

T2E的轉換 賦值給空interface{},運行時會調用conv類函數convT2E。與convT2I一樣,向interface的類型轉換var i interface{} = u都存在記憶體拷貝。看iface.go源碼時發現convI2I等介面轉換時沒有分配記憶體和拷貝資料,原因可能是Go介面內部的data域,並不開放途徑讓外部修改,所以介面之間轉換可以用同一塊記憶體。

func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e interface{}) {ep := (*eface)(unsafe.Pointer(&e))  ...if x == nil {x = newobject(t)}typedmemmove(t, x, elem)ep._type = tep.data = x}return}

下面的例子中修改u並不影響i interface的記憶體資料,因為i在賦值時通過convT2E對u進行了拷貝。這也是反射非指標變數時無法直接改變變數資料的原因,因為反射會先把變數轉成interface類型,拿到的是變數的副本。

u := User{1, "Tom"}var i interface{} = uu.id = 2u.name = "Jack"// u {2, "Jack"}// i.(User) {1, "Tom"}

nil的理解

未初始化的interface類型,指標,函數,slice,cannel和map都是nil的。對於interface比較特殊,只有eface的type和data都是nil,或者iface的type和data都是nil時,interface{}才是nil。

type Duck interface{    Walk()}var i interface{}   // nilvar d Duck   // nilvar v *T   // nili = v   // (*T)(nil) not nil

type assertion

嚴格來說Go並不支援泛型程式設計,但通過interface可實現泛型程式設計,後面reflect淺析中有個通過reflect實現泛型的例子。interface像其它類型轉換的時候一般需要斷言。下面只給出了eface的例子,當然也可以通過斷言來判斷某個類型是否實現了某個介面。

func do(v interface{}) {n, ok := v.(int)if !ok {...}}func doswitch(i interface{}) {switch v := i.(type) {case int: ...}}

對應的go源碼在iface.go當中。assertE2T過程判斷了eface的type欄位是否和目標type相等,相等則還需要拷貝資料。assertI2T也要拷貝資料,不過他比較的是iface.tab._type與目標type是否一致。

func assertE2T(t *_type, e interface{}, r unsafe.Pointer) {ep := (*eface)(unsafe.Pointer(&e))if ep._type == nil {panic(&TypeAssertionError{"", "", *t._string, ""})}if ep._type != t {panic(&TypeAssertionError{"", *ep._type._string, *t._string, ""})}if r != nil {if isDirectIface(t) {writebarrierptr((*uintptr)(r), uintptr(ep.data))} else {typedmemmove(t, r, ep.data)}}}

reflect淺析

反射機制提供了檢查儲存在介面變數中的[類型 值]對的機制。根據Laws Of ReflectionGo的反射可以總結三點,即反射可以從interface中擷取reflect對象;同時可以通過Interface()方法恢複reflect對象為一個interface;如果要修改反射對象,該對象必須是settable的。

TypeOf與ValueOf實現

擷取反射對象的實現,是基於對interface底層資料的操作。首先對象經過了convT2E,然後emptyInterface直接指向了eface的type欄位。typeOf返回的Type是介面,在類型_type上實現了很多操作。valueOf返回的就是Value結構體,它包含了資料域和type域資訊。

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)}func unpackEface(i interface{}) Value {e := (*emptyInterface)(unsafe.Pointer(&i))t := e.typif t == nil {return Value{}}f := flag(t.Kind())if ifaceIndir(t) {f |= flagIndir}return Value{t, unsafe.Pointer(e.word), f}}
type Type interface{  ...Method(int) MethodMethodByName(string) (Method, bool)NumMethod() intNumField()Field(i) StructFieldFieldByName(name string) (StructField, bool)  ...}type Value struct {typ *rtypeptr unsafe.Pointerflag}

通過Type擷取Struct Field資訊

可以通過reflect擷取類型執行個體的結構體資訊,比如每個field的名字類型或標籤。

// A StructField describes a single field in a struct.type StructField struct {Name string  // Name is the field name.PkgPath stringType      Type      // field typeTag       StructTag // field tag stringOffset    uintptr   // offset within struct, in bytesIndex     []int     // index sequence for Type.FieldByIndexAnonymous bool      // is an embedded field}type Person struct {Name string `json:"name"`Age  int    `json:"age"`}func main() {nino := Person{"nino", 27}t := reflect.TypeOf(nino)n := t.NumField()for i := 0; i < n; i++ {fmt.Println(t.Field(i).Name, t.Field(i).Type, t.Field(i).Tag)}}// Name string json:"name"// Age int json:"age"

通過Value實現泛型

為瞭解決method接受不同類型的slice為入參,可以用反射來完成。對於可記長度和可隨機訪問的類型,可以通過v.Len()v.Index(i)擷取他們的第幾個元素。

v.Index(i).Interface()將reflect.Value反射回了interface類型

func method(in interface{}) (ok bool) {v := reflect.ValueOf(in)if v.Kind() == reflect.Slice {ok = true} else {return false}num := v.Len()for i := 0; i < num; i++ {fmt.Println(v.Index(i).Interface())}return ok}func main() {s := []int{1, 3, 5, 7, 9}b := []float64{1.2, 3.4, 5.6, 7.8}method(s)method(b)}

通過Elem修改reflect對象值

對LawsOfReflect第三點的理解 reflect.ValueOf 如果直接傳入x,則v是x的一個副本的reflect對象。修改v的值並不會作用到x上。p是指向x的指標的reflect對象,修改p的值是在修改指標的指向,同樣不會作用到x上,因此也是CanNotSet的。只有通過p.Elem()相當於擷取了*p的reflect對象,這時才能使用v.SetFloat(7.2)來對原始的資料進行修改。

var x float64 = 3.4v := reflect.ValueOf(x)   // can not setp := reflect.ValueOf(&x)  // can not sete := p.Elem()  // can set
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.