前言
本文将主要介绍golang中的interface{},解开他的神秘面纱,介绍它之前,我们需要先了解golang 的类型系统,然后介绍接口的使用,接口的底层原理,以及接口在反射中的原理
类型系统
Golang的内置类型(build-in)有 int8
int16
int32
int64
int
float
byte
string
slice
map
chan
func
等等,当然我们也可以定义自定义的类型如
type MyInt int
type 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.go
type _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
的类型元数据就是
接口
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.go
type 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.go
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 //这里虽然在运行时只定义了大小为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.go
type 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.go
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
TypeOf
方法将空接口转换成具体的类型,该Type是个接口,提供了很多获取元数据方法
//$GOROOT/src/reflect/value.go
type 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.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
上面不能修改是因为x 是个值类型,v.SetFloat 修改的是x 的值拷贝的内容,没有意义,故golang 不允许该场景出现,会panic
那我们需要怎么修改呢,可以传入它的指针,如下
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type()) //type of p: *float64
fmt.Println("settability of p:", p.CanSet())//settability of p: false ,不能修改指针,只能修改指针指向的内容
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())//settability of v: true
v.SetFloat(7.1)
fmt.Println(v.Interface()) //7.1
fmt.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 = skidoo
s.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的关系,如何通过反射修改底层动态类型的值等
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。