go中的類型轉換成interface之后如何復原


go中interface轉換成原來的類型

首先了解下interface

什么是interface?

首先 interface 是一種類型,從它的定義可以看出來用了 type 關鍵字,更准確的說 interface 是一種具有一組方法的類型,這些方法定義了 interface 的行為。

type I interface {
    Get() int
}

interface是一組method的集合,是duck-type programming的一種體現(不關心屬性(數據),只關心行為(方法))。我們可以自己定義interface類型的struct,並提供方法。

type MyInterface interface{
    Print()
}

func TestFunc(x MyInterface) {}
type MyStruct struct {}
func (me MyStruct) Print() {}

func main() {
    var me MyStruct
    TestFunc(me)
}

go 允許不帶任何方法的 interface ,這種類型的 interfaceempty interface

如果一個類型實現了一個 interface 中所有方法,必須是所有的方法,我們說類型實現了該 interface,所以所有類型都實現了 empty interface,因為任何一種類型至少實現了 0 個方法。go 沒有顯式的關鍵字用來實現 interface,只需要實現 interface 包含的方法即可。

interface還可以作為返回值使用。

如何判斷interface變量存儲的是哪種類型

日常中使用interface,有時候需要判斷原來是什么類型的值轉成了interface。一般有以下幾種方式:

fmt
import "fmt"
func main() {
    v := "hello world"
    fmt.Println(typeof(v))
}
func typeof(v interface{}) string {
    return fmt.Sprintf("%T", v)
}
反射
import (
    "reflect"
    "fmt"
)
func main() {
    v := "hello world"
    fmt.Println(typeof(v))
}
func typeof(v interface{}) string {
    return reflect.TypeOf(v).String()
}
斷言

Go語言里面有一個語法,可以直接判斷是否是該類型的變量: value, ok = element.(T),這里value就是變量的值,ok是一個bool類型,elementinterface變量,T是斷言的類型。

如果element里面確實存儲了T類型的數值,那么ok返回true,否則返回false

讓我們通過一個例子來更加深入的理解。

value, ok := v.(string)

if ok {
    return value
}

類型不確定可以配合switch:

func main() {
    v := "hello world"
    fmt.Println(typeof(v))
}
func typeof(v interface{}) string {
    switch t := v.(type) {
    case int:
        return "int"
    case float64:
        return "float64"
    //... etc
    default:
        _ = t
        return "unknown"
    }
}

對於fmt也是用了反射的,同時里面也用到了斷言:

func (p *pp) printArg(arg interface{}, verb rune) {
	p.arg = arg
	p.value = reflect.Value{}

	if arg == nil {
		switch verb {
		case 'T', 'v':
			p.fmt.padString(nilAngleString)
		default:
			p.badVerb(verb)
		}
		return
	}

	// Special processing considerations.
	// %T (the value's type) and %p (its address) are special; we always do them first.
	switch verb {
	case 'T':
		p.fmt.fmtS(reflect.TypeOf(arg).String())
		return
	case 'p':
		p.fmtPointer(reflect.ValueOf(arg), 'p')
		return
	}

	// Some types can be done without reflection.
	switch f := arg.(type) {
	case bool:
		p.fmtBool(f, verb)
	case float32:
		p.fmtFloat(float64(f), 32, verb)
	case float64:
		p.fmtFloat(f, 64, verb)
	case complex64:
		p.fmtComplex(complex128(f), 64, verb)
	case complex128:
		p.fmtComplex(f, 128, verb)
	case int:
		p.fmtInteger(uint64(f), signed, verb)
	case int8:
		p.fmtInteger(uint64(f), signed, verb)
	case int16:
		p.fmtInteger(uint64(f), signed, verb)
	case int32:
		p.fmtInteger(uint64(f), signed, verb)
	case int64:
		p.fmtInteger(uint64(f), signed, verb)
	case uint:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint8:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint16:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint32:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint64:
		p.fmtInteger(f, unsigned, verb)
	case uintptr:
		p.fmtInteger(uint64(f), unsigned, verb)
	case string:
		p.fmtString(f, verb)
	case []byte:
		p.fmtBytes(f, verb, "[]byte")
	case reflect.Value:
		// Handle extractable values with special methods
		// since printValue does not handle them at depth 0.
		if f.IsValid() && f.CanInterface() {
			p.arg = f.Interface()
			if p.handleMethods(verb) {
				return
			}
		}
		p.printValue(f, verb, 0)
	default:
		// If the type is not simple, it might have methods.
		if !p.handleMethods(verb) {
			// Need to use reflection, since the type had no
			// interface methods that could be used for formatting.
			p.printValue(reflect.ValueOf(f), verb, 0)
		}
	}
}

下面來簡單探究下反射是如何判斷interface

// 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)
}

eface := *(*emptyInterface)(unsafe.Pointer(&i))用到了一個emptyInterface,我們來看下這個結構的信息:

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}

其中typ指向一個rtype實體, 它表示interface的類型以及賦給這個interface的實體類型。word則指向interface具體的值,一般而言是一個指向堆內存的指針。

TypeOf看到的是空接口interface{},它將變量的地址轉換為空接口,然后將得到的rtype轉為Type接口返回。需要注意,當調用reflect.TypeOf的之前,已經發生了一次隱式的類型轉換,即將具體類型的向空接口轉換。這個過程比較簡單,只要拷貝typ *rtypeword unsafe.Pointer就可以了。

來看下interface的底層源碼

我的go版本是go version go1.13.7

ifaceeface都是Go中描述接口的底層結構體,區別在於iface描述的接口包含方法,而eface則是不包含任何方法的空接口:interface{}

eface

代碼在runtime/runtime2.go:

type eface struct {
	_type *_type
	data  unsafe.Pointer
}

eface有兩個字段,_type指向對象的類型信息,data數據指針。指針指向的數據地址,一般是在堆上的。

我們來看下_type

// src/rumtime/runtime2.go
type _type struct {
    size       uintptr     // 類型的大小
    ptrdata    uintptr     // size of memory prefix holding all pointers
    hash       uint32      // 類型的Hash值
    tflag      tflag       // 類型的Tags 
    align      uint8       // 結構體內對齊
    fieldalign uint8       // 結構體作為field時的對齊
    kind       uint8       // 類型編號 定義於runtime/typekind.go
    alg        *typeAlg    // 類型元方法 存儲hash和equal兩個操作。map key便使用key的_type.alg.hash(k)獲取hash值
    gcdata    *byte        // GC相關信息
    str       nameOff      // 類型名字的偏移    
    ptrToThis typeOff    
}

_typego中類型的公共描述,里面包含GC,反射等需要的細節,它決定data應該如何解釋和操作。對於不同的數據類型它的描述信息是不一樣的,在_type的基礎之上配合一些額外的描述信息,來進行區分。

// src/runtime/type.go
// ptrType represents a pointer type.
type ptrType struct {
   typ     _type   // 指針類型 
   elem  *_type // 指針所指向的元素類型
}
type chantype struct {
    typ  _type        // channel類型
    elem *_type     // channel元素類型
    dir  uintptr
}
type maptype struct {
    typ           _type
    key           *_type
    elem          *_type
    bucket        *_type // internal type representing a hash bucket
    hmap          *_type // internal type representing a hmap
    keysize       uint8  // size of key slot
    indirectkey   bool   // store ptr to key instead of key itself
    valuesize     uint8  // size of value slot
    indirectvalue bool   // store ptr to value instead of value itself
    bucketsize    uint16 // size of bucket
    reflexivekey  bool   // true if k==k for all keys
    needkeyupdate bool   // true if we need to update key on an overwrite
}

這些類型信息的第一個字段都是_type(類型本身的信息),接下來是一堆類型需要的其它詳細信息(如子類型信息),這樣在進行類型相關操作時,可通過一個字(typ *_type)即可表述所有類型,然后再通過_type.kind可解析出其具體類型,最后通過地址轉換即可得到類型完整的”_type樹”,參考reflect.Type.Elem()函數:

// reflect/type.go
// reflect.rtype結構體定義和runtime._type一致  type.kind定義也一致(為了分包而重復定義)
// Elem()獲取rtype中的元素類型,只針對復合類型(Array, Chan, Map, Ptr, Slice)有效
func (t *rtype) Elem() Type {
   switch t.Kind() {
   case Array:
      tt := (*arrayType)(unsafe.Pointer(t))
      return toType(tt.elem)
   case Chan:
      tt := (*chanType)(unsafe.Pointer(t))
      return toType(tt.elem)
   case Map:
      // 對Map來講,Elem()得到的是其Value類型
      // 可通過rtype.Key()得到Key類型
      tt := (*mapType)(unsafe.Pointer(t))
      return toType(tt.elem)
   case Ptr:
      tt := (*ptrType)(unsafe.Pointer(t))
      return toType(tt.elem)
   case Slice:
      tt := (*sliceType)(unsafe.Pointer(t))
      return toType(tt.elem)
   }
   panic("reflect: Elem of invalid type")
}

iface

表示的是非空的接口:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.
type itab struct {
	inter *interfacetype  // 接口定義的類型信息
	_type *_type          // 接口實際指向值的類型信息
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr     // 接口方法實現列表,即函數地址列表,按字典序排序 variable sized
}
// runtime/type.go
// 非空接口類型,接口定義,包路徑等。
type interfacetype struct {
   typ     _type
   pkgpath name
   mhdr    []imethod      // 接口方法聲明列表,按字典序排序
}

// 接口的方法聲明 
type imethod struct {
   name nameOff              // 方法名
   ityp typeOff              // 描述方法參數返回值等細節
}

iface同樣也是有兩個指針,tab指向一個itab實體, 它表示接口的類型以及賦給這個接口的實體類型。data則指向接口具體的值,一般而言是一個指向堆內存的指針。

fun表示interfacemethod的具體實現。比如interfacetype包含了兩個method分別是AB。但是有一點很奇怪,這個fun是長度為1的uintptr數組,那么是怎么表示多個的呢?
其實上面源碼的注釋已經能給到我們答案了,variable sized,這是個是可變大小的。go中的uintptr一般用來存放指針的值,那這里對應的就是函數指針的值(也就是函數的調用地址)。如果有更多的方法,在它之后的內存空間里繼續存儲。也就是在fun[0]后面一次寫入其他method對應的函數指針。

接口的類型轉換是怎么實現的呢?

舉個例子:

type coder interface {
	code()
	run()
}

type runner interface {
	run()
}

type Gopher struct {
	language string
}

func (g Gopher) code() {
	return
}

func (g Gopher) run() {
	return
}

func main() {
	var c coder = Gopher{}

	var r runner
	r = c
	fmt.Println(c, r)
}

定義了兩個 interface: coderrunner。定義了一個實體類型 Gopher,類型 Gopher 實現了兩個方法,分別是 run()code()main 函數里定義了一個接口變量 c,綁定了一個 Gopher 對象,之后將 c 賦值給另外一個接口變量 r 。賦值成功的原因是 c 中包含 run() 方法。這樣,兩個接口變量完成了轉換。

上面的轉換調用了下面的函數實現的

func convI2I(inter *interfacetype, i iface) (r iface) {
	tab := i.tab
	if tab == nil {
		return
	}
	if tab.inter == inter {
		r.tab = tab
		r.data = i.data
		return
	}
	r.tab = getitab(inter, tab._type, false)
	r.data = i.data
	return
}

關於conv的函數定義,其中E代表eface,I代表iface,T代表編譯器已知類型,即靜態類型。

inter表示轉換之后的接口類型,i表示轉換之前的實體類型接口,r表示轉換之后的實體類型接口。
這個函數先做了判斷,如果兩個轉換之前和轉換之后的接口類型是一樣的,就直接把轉換之前的接口信息賦值給r就可以了。如果不一樣,就調用getitab

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
	if len(inter.mhdr) == 0 {
		throw("internal error - misuse of itab")
	}

	// easy case
	if typ.tflag&tflagUncommon == 0 {
		if canfail {
			return nil
		}
		name := inter.typ.nameOff(inter.mhdr[0].name)
		panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
	}

	var m *itab

	// First, look in the existing table to see if we can find the itab we need.
	// This is by far the most common case, so do it without locks.
	// Use atomic to ensure we see any previous writes done by the thread
	// that updates the itabTable field (with atomic.Storep in itabAdd).
	t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
	if m = t.find(inter, typ); m != nil {
		goto finish
	}

	// Not found.  Grab the lock and try again.
	lock(&itabLock)
	if m = itabTable.find(inter, typ); m != nil {
		unlock(&itabLock)
		goto finish
	}

	// Entry doesn't exist yet. Make a new entry & add it.
	m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
	m.inter = inter
	m._type = typ
	m.init()
	itabAdd(m)
	unlock(&itabLock)
finish:
	if m.fun[0] != 0 {
		return m
	}
	if canfail {
		return nil
	}
	// this can only happen if the conversion
	// was already done once using the , ok form
	// and we have a cached negative result.
	// The cached result doesn't record which
	// interface function was missing, so initialize
	// the itab again to get the missing function name.
	panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

簡單總結一下:getitab 函數會根據 interfacetype_type 去全局的 itab 哈希表中查找,如果能找到,則直接返回;否則,會根據給定的 interfacetype_type 新生成一個 itab,並插入到 itab 哈希表,這樣下一次就可以直接拿到 itab
第一次去查詢的時候如果查找到,直接返回

if m = t.find(inter, typ); m != nil {
		goto finish
	}

如果在hash表中沒有找到,這時候鎖住itabLock,然后去重新寫入itab到哈希表,當寫入之后,上游的查詢拿到值了,解除鎖的阻塞,然后返回。

if m = itabTable.find(inter, typ); m != nil {
		unlock(&itabLock)
		goto finish
	}

再來看一下 itabAdd 函數的代碼:

// itabAdd adds the given itab to the itab hash table.
// itabLock must be held.
func itabAdd(m *itab) {
	// Bugs can lead to calling this while mallocing is set,
	// typically because this is called while panicing.
	// Crash reliably, rather than only when we need to grow
	// the hash table.
	if getg().m.mallocing != 0 {
		throw("malloc deadlock")
	}

	t := itabTable
	if t.count >= 3*(t.size/4) { // 75% load factor
		// Grow hash table.
		// t2 = new(itabTableType) + some additional entries
		// We lie and tell malloc we want pointer-free memory because
		// all the pointed-to values are not in the heap.
		t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
		t2.size = t.size * 2

		// Copy over entries.
		// Note: while copying, other threads may look for an itab and
		// fail to find it. That's ok, they will then try to get the itab lock
		// and as a consequence wait until this copying is complete.
		iterate_itabs(t2.add)
		if t2.count != t.count {
			throw("mismatched count during itab table copy")
		}
		// Publish new hash table. Use an atomic write: see comment in getitab.
		atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
		// Adopt the new table as our own.
		t = itabTable
		// Note: the old table can be GC'ed here.
	}
	t.add(m)
}

最后總結下:

  • 1、具體類型轉空接口時,_type 字段直接復制源類型的 _type;調用 mallocgc 獲得一塊新內存,把值復制進去,data 再指向這塊新內存。
  • 2、具體類型轉非空接口時,入參 tab 是編譯器在編譯階段預先生成好的,新接口 tab 字段直接指向入參 tab 指向的 itab;調用 mallocgc 獲得一塊新內存,把值復制進去,data 再指向這塊新內存。
  • 3、而對於接口轉接口,itab 調用 getitab 函數獲取。只用生成一次,之后直接從 hash 表中獲取。

接口的動態類型和動態值

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

iface我們可以看到,是有一個tab接口指針,指向數據類型,data數據指針,指向具體的數據。他們也被稱為動態類型動態值
因為兩個都是指針,所以默認值都是nil。所以當兩者都是nil的時候這個接口值才是nil,也就是接口值 == nil

func main() {
	var f interface{}
	fmt.Println("+++動態類型和動態值都是nil+++")
	fmt.Println(f == nil)
	fmt.Printf("f: %T, %v\n", f, f)

	var g *string
	f = g
	fmt.Println("+++類型為 *string+++")
	fmt.Println(f == nil)
	fmt.Printf("f: %T, %v\n", f, f)
}

打印下輸出:

+++動態類型和動態值都是nil+++
true
f: <nil>, <nil>
+++類型為 *string+++
false
f: *string, <nil> 

interface如何支持泛型

嚴格來說,在 Golang 中並不支持泛型編程。在 C++ 等高級語言中使用泛型編程非常的簡單,所以泛型編程一直是 Golang 詬病最多的地方。但是使用 interface 我們可以實現“泛型編程”,為什么?因為 interface 是一種抽象類型,任何具體類型(int, string)和抽象類型(user defined)都可以封裝成 interface。以標准庫的 sort 為例。

package sort

// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package.  The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

...

// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
    // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
    n := data.Len()
    maxDepth := 0
    for i := n; i > 0; i >>= 1 {
        maxDepth++
    }
    maxDepth *= 2
    quickSort(data, 0, n, maxDepth)
}

Sort 函數的形參是一個 interface,包含了三個方法:Len(),Less(i,j int),Swap(i, j int)。使用的時候不管數組的元素類型是什么類型(int, float, string…),只要我們實現了這三個方法就可以使用 Sort 函數,這樣就實現了“泛型編程”。有一點比較麻煩的是,我們需要自己封裝一下。下面是一個例子。

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s: %d", p.Name, p.Age)
}

// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person //自定義

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

func main() {
    people := []Person{
        {"Bob", 31},
        {"John", 42},
        {"Michael", 17},
        {"Jenny", 26},
    }

    fmt.Println(people)
    sort.Sort(ByAge(people))
    fmt.Println(people)
}

具體一點來說,也就是如果是在實現一個服務時,對於不同場景,可以將其共同特征抽象出來,在一個interface中聲明,然后給不同的場景定義其特定的struct,上層的邏輯可以通過傳入interface來執行,特化則通過struct實現對應的方法,從而達到一定程度的泛型。

參考

【理解 Go interface 的 5 個關鍵點】https://sanyuesha.com/2017/07/22/how-to-understand-go-interface/
【深入理解 Go Interface】https://zhuanlan.zhihu.com/p/32926119
【GO如何支持泛型】https://zhuanlan.zhihu.com/p/74525591
【Golang面向對象編程】https://code.tutsplus.com/zh-hans/tutorials/lets-go-object-oriented-programming-in-golang--cms-26540
【深度解密Go語言之關於 interface 的10個問題】https://www.cnblogs.com/qcrao-2018/p/10766091.html
【golang如何獲取變量的類型:反射,類型斷言】https://ieevee.com/tech/2017/07/29/go-type.html
【Go接口詳解】https://zhuanlan.zhihu.com/p/27055513


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM