前言
本文将次要介绍golang中的interface{},解开他的神秘面纱,介绍它之前,咱们须要先理解golang 的类型零碎,而后介绍接口的应用,接口的底层原理,以及接口在反射中的原理
类型零碎
Golang的内置类型(build-in)有 int8
int16
int32
int64
int
float
byte
string
slice
map
chan
func
等等,当然咱们也能够定义自定义的类型如
type MyInt inttype T struct{ name string }type I interface{ Name() string}
留神:
- 不能给内置类型定义办法,然而能够给
MyInt
这个自定义类型定义办法,这里须要区别于type MyInt2= int
,这里MyInt2
是int
的别名,实质是同一类型,而MyInt
尽管底层类型是int
然而属于一种全新的自定义类型 接口类型是有效的办法接收者。如
func (i I)foo()// 编译器会报错
不论是内置类型还是自定义类型信息都有类型元数据,每种类型元数据都有全局惟一的类型形容,这里有点相似Java中的Class信息。
那么类型元数据长什么样呢?
//$GOROOT/src/runtime.type.gotype _type struct { size uintptr ptrdata uintptr // size of memory prefix holding all pointers hash uint32 tflag tflag align uint8 fieldAlign uint8 kind uint8 // function for comparing objects of this type // (ptr to object A, ptr to object B) -> ==? equal func(unsafe.Pointer, unsafe.Pointer) bool // 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}func (t *_type) uncommon() *uncommontype { if t.tflag&tflagUncommon == 0 { return nil } switch t.kind & kindMask { case kindStruct: type u struct { structtype u uncommontype } return &(*u)(unsafe.Pointer(t)).u ...//为了解说不便这里省略一些其余的类型 case kindSlice: type u struct { slicetype u uncommontype } return &(*u)(unsafe.Pointer(t)).u }}//其余形容信息type uncommontype struct { pkgpath nameOff mcount uint16 // number of methods xcount uint16 // number of exported methods moff uint32 // offset from this uncommontype to [mcount]method _ uint32 // unused}//内置的slice类型type slicetype struct { typ _type elem *_type //切片中寄存元素的类型指针,如 []int ,则elem指向int的类型元数据的指针inttype}
每个类型元信息下还有一些其余形容信息uncommontype
,外面记录了包门路,办法数目,寄存办法元数据数据的偏移。
例如上述的MyInt
定义一些办法如下所示
type MyInt struct func(m MyInt)Hello(){ fmt.Println("hello")}
那么MyInt
的类型元数据就是
<img src="https://tva1.sinaimg.cn/large/8dfd1ceegy1h028vkuvpoj20qk0hiac3.jpg" alt="image" style="zoom:50%;" />
接口
duck typing 是程序设计中的动静格调,艰深来讲,
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就能够被称为鸭子。
也就是说关注点在对象的行为,而不再对象自身。
golang 应用 "structural typing" 相似"duck typing",只不过它产生在编译阶段。
type Reader interface { Read(p []byte) (n int, err error)}type Writer interface { Write(p []byte) (n int, err error)}type File struct { *file // os specific}func (f *File) Read(b []byte) (n int, err error) { if err := f.checkValid("read"); err != nil { return 0, err } n, e := f.read(b) return n, f.wrapErr("read", e)}func (f *File) Write(b []byte) (n int, err error) { if err := f.checkValid("write"); err != nil { return 0, err } n, e := f.write(b) if n < 0 { n = 0 } if n != len(b) { err = io.ErrShortWrite } epipecheck(f, e) if e != nil { err = f.wrapErr("write", e) } return n, err}
例如io 包中定义的Reader
和Writer
的接口,os 包中的File
构造实现了这两个接口,那么其实File就算实现了这个接口,而不向Java 等语言须要显示implement 相应的接口能力认为实现该接口。
接口底层构造
接口次要蕴含空接口interface{}和非空接口(如上述提到的Reader
和Writer
),上面咱们来看看空接口和非空接口底层数据接口是怎么示意的
interface{}
//$GOROOT/src/runtime/runtime2.gotype eface struct { _type *_type data unsafe.Pointer}
其中,_type
指向的是动静类型的元数据,data
指向的是动静类型的值,例如
func main() { var ifc interface{} f, _ := os.Open("main.go") ifc = f fmt.Println(ifc)}
赋值前 ifc
的_type
和data
都是nil, f
是*os.File
,那么赋值后,_type
指向*os.File
的类型元数据(外面蕴含了构造体的Filed信息和办法Method数组),data
指向f
非空接口
//$GOROOT/src/runtime/runtime2.gotype 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 //这里尽管在运行时只定义了大小为1的数据,然而其存储的是函数首地址的指针,当有多个函数时,指针会顺次存储在数据下方,能够通过首地址+offset 找到}type interfacetype struct { typ _type //接口的元数据 pkgpath name //包名 mhdr []imethod//接口定义的办法列表}
与空接口一样,data
指向理论的动静值,itab
是接口的外围,外面记录了接口类型元数据inter
和动静类型_type
,其中fun
是将inter
接口类型元数据中定义的接口办法在理论动静类型_type
中的实现的拷贝。
上图援用自 [[幼麟实验室]Golang接口 ](https://www.bilibili.com/vide...)
须要留神的是 当接口类型和动静类型确定之后,itab
也就固定了,所以golang 会将用到的itab
缓存起来,以接口类型和动静类型为key 以itab
指针为value 寄存在runtime.itabTableType
这个哈希表
//$GOROOT/src/runtime/iface.gotype itabTableType struct { size uintptr // length of entries array. Always a power of 2. count uintptr // current number of filled entries. entries [itabInitSize]*itab // really [size] large}func itabHashFunc(inter *interfacetype, typ *_type) uintptr { // compiler has provided some good hash codes for us. return uintptr(inter.typ.hash ^ typ.hash)}
留神其中itabTableType
应用凋谢寻址法来解决hash抵触,不同于罕用的golang中的map
,其中itabHashFunc
是用来计算key,由接口类型的类型hash 和动静值类型的类型hash 异或失去。若哈希表中没有对应的key/value, 则创立并增加到表中。
类型断言
在业务编码中咱们进场须要进行类型接口类型转换,这就须要应用接口断言
var ifc interface{} f, _ := os.Open("main.go") ifc = f reader,ok :=ifc.(io.Reader) //类型断言办法一switch ifc.(type) { //类型断言办法二 case io.Reader: reader := ifc.(io.Reader) default: fmt.Println("assert type fail") }
以上是两种办法断言,所以类型断言能够分为 以下四种状况
- 空接口.(具体类型)
- 非空接口.(具体类型)
- 空接口.(非空接口)
- 非空接口.(非空接口)
空接口.(具体类型)
咱们只须要判断空接口eface
的_type
是否和动静类型统一即可,如 f,ok:=ifc.(*os.File)
,ifc底层是*os.File
,故ok为true
非空接口.(具体类型)
依照咱们后面讲到的runtime.itabTableType
寄存了itab
的缓存,那么咱们类型断言的时候只须要以 非空接口和具体类型为key ,查找哈希表中的itab
,若找到的itab
指针和iface
中的itab统一,那么类型断言胜利
空接口.(非空接口)
咱们晓得eface
中包含动静至的类型_type
,那么咱们能够以这个非空接口和动静类型为key去runtime.itabTableType
缓存中查找itab
,若找到了,则阐明该空接口类型断言胜利,若找不到,则查找该动静类型的uncommontype
的办法列表是否都实现了非空接口interfacetype
中定义的全副办法mhdr
,并将后果组装成新的itab
插入到runtime.itabTableType
,一遍下次类型断言能疾速判断。
这里须要留神的是,若该空接口的动静类型_type
没有实现该空接口interfacetype
中的办法,也会组装成一个itab
退出到缓存,只不过该itab
的fun[0]=0
例如f,ok:=ifc.(io.ReaderWriter)
,会首先用ifc 的动静类型*os.File
和io.ReaderWriter
为key,在runtime.itabTableType
中查找itab
,若查找到,且func[0]!=0,则类型断言胜利,若func[0]==0,类型断言失败。若查找不到,则比拟*os.File
的uncommontype
是否都实现了io.ReaderWriter
的接口定义办法,并组装itab
退出到哈希表中
非空接口.(非空接口)
办法和下面相似,咱们间接以例子来解说
var r io.Reader f, _ := os.Open("main.go") r = f rw,ok := r.(io.ReadWriter)
咱们能够从r或获取inter
中的_type
的动静类型*os.File
,而后将io.ReadWriter
和*os.File
为key ,在runtime.itabTableType
中查找itab,后续步骤上 同空接口.(非空接口) 类型转换的流程,不在赘述。
反射
下面咱们曾经晓得的类型的元数据,其定义在runtime包下,是未导出的,为了在运行时获取这写类型数据并就行反射调用,reflect 包中定义了一套一样的导出的类型构造,如下图所示
1. interface{} 转 reflect.Type
//$GOROOT/src/reflect/type.gofunc TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ)}
TypeOf
办法将空接口转换成具体的类型,该Type是个接口,提供了很多获取元数据办法
//$GOROOT/src/reflect/value.gotype Type interface { Method(int) Method MethodByName(string) (Method, bool) NumMethod() int Name() string PkgPath() string Kind() Kind Implements(u Type) bool AssignableTo(u Type) bool Comparable() bool ... common() *rtype uncommon() *uncommonType}
2. 通过reflect.Value 批改值
type Value struct { // typ holds the type of the value represented by a Value. typ *rtype // Pointer-valued data or, if flagIndir is set, pointer to data. // Valid when either flagIndir is set or typ.pointers() is true. ptr unsafe.Pointer // flag holds metadata about the value. // The lowest bits are flag bits: // - flagStickyRO: obtained via unexported not embedded field, so read-only // - flagEmbedRO: obtained via unexported embedded field, so read-only // - flagIndir: val holds a pointer to the data // - flagAddr: v.CanAddr is true (implies flagIndir) // - flagMethod: v is a method value. // The next five bits give the Kind of the value. // This repeats typ.Kind() except for method values. // The remaining 23+ bits give a method number for method values. // If flag.kind() != Func, code can assume that flagMethod is unset. // If ifaceIndir(typ), code can assume that flagIndir is set. flag // A method value represents a curried method invocation // like r.Read for some receiver r. The typ+val+flag bits describe // the receiver r, but the flag's Kind bits say Func (methods are // functions), and the top bits of the flag give the method number // in r's type's method table.}
下面是Value的反射构造,能够通过它来获取或批改它所指向的内容(当然批改须要这个值是个指针类型)
// 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 { if i == nil { return Value{} } // TODO: Maybe allow contents of a Value to live on the stack. // For now we make the contents always escape to the heap. It // makes life easier in a few places (see chanrecv/mapassign // comment below). escapes(i) return unpackEface(i)}
ValueOf 相似于TypeOf 提供了interface{} 向反射值的转换方法。
接下来咱们看下如果通过它来批改值
var x float64 = 3.4v := reflect.ValueOf(x)v.SetFloat(7.1) // Error: will panic.
下面不能批改是因为x 是个值类型,v.SetFloat 批改的是x 的值拷贝的内容,没有意义,故golang 不容许该场景呈现,会panic
那咱们须要怎么批改呢,能够传入它的指针,如下
var x float64 = 3.4p := reflect.ValueOf(&x) // Note: take the address of x.fmt.Println("type of p:", p.Type()) //type of p: *float64fmt.Println("settability of p:", p.CanSet())//settability of p: false ,不能批改指针,只能批改指针指向的内容v := p.Elem()fmt.Println("settability of v:", v.CanSet())//settability of v: truev.SetFloat(7.1)fmt.Println(v.Interface()) //7.1fmt.Println(x)//7.1
同样构造体也能够通过传递指针的形式,批改他的值
type T struct { A int B string}t := T{23, "skidoo"}s := reflect.ValueOf(&t).Elem()typeOfT := s.Type()for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface())}//0: A int = 23//1: B string = skidoos.Field(0).SetInt(77)s.Field(1).SetString("Sunset Strip")fmt.Println("t is now", t)//t is now {77 Sunset Strip}
3. reflect.Value转换interface{}
当咱们通过反射获取reflect.Value 之后,常常须要将它转换到他的原始类型进行应用,这是咱们须要先将其转化成interface{},再通过类型转换到具体类型后应用
// Interface returns v's value as an interface{}.func (v Value) Interface() interface{}
例如
v=reflect.ValueOf(3.4)y := v.Interface().(float64) // y will have type float64.fmt.Println(y)
总结
本文介绍了golang的类型零碎,以及接口在底层包含空接口eface和非空接口iface,曾经其在底层的数据接口,介绍了类型转换的底层机制,以及反射中的reflect.Type 和reflect.Value 与空接口eface和非空接口iface的关系,如何通过反射批改底层动静类型的值等
参考文献
- Duck typing in Go
- The Laws of Reflection
- https://zh.wikipedia.org/wiki...
- https://www.bilibili.com/vide...