乐趣区

关于后端:深入浅出Golang-interface

前言

本文将次要介绍 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
}

留神:

  1. 不能给内置类型定义办法,然而能够给 MyInt 这个自定义类型定义办法,这里须要区别于 type MyInt2= int, 这里 MyInt2int的别名,实质是同一类型,而 MyInt 尽管底层类型是int 然而属于一种全新的自定义类型
  2. 接口类型是有效的办法接收者。如

    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 的类型元数据就是

<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 包中定义的 ReaderWriter的接口,os 包中的 File 构造实现了这两个接口,那么其实 File 就算实现了这个接口,而不向 Java 等语言须要显示 implement 相应的接口能力认为实现该接口。

接口底层构造

接口次要蕴含空接口 interface{}和非空接口(如上述提到的 ReaderWriter), 上面咱们来看看空接口和非空接口底层数据接口是怎么示意的

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_typedata都是 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")
    }

以上是两种办法断言,所以类型断言能够分为 以下四种状况

  1. 空接口.(具体类型)
  2. 非空接口.(具体类型)
  3. 空接口.(非空接口)
  4. 非空接口.(非空接口)

空接口.(具体类型)

咱们只须要判断空接口 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.Fileio.ReaderWriter为 key, 在 runtime.itabTableType 中查找 itab, 若查找到,且 func[0]!=0, 则类型断言胜利,若 func[0]==0,类型断言失败。若查找不到,则比拟*os.Fileuncommontype是否都实现了 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 的关系,如何通过反射批改底层动静类型的值等

参考文献

  1. Duck typing in Go
  2. The Laws of Reflection
  3. https://zh.wikipedia.org/wiki…
  4. https://www.bilibili.com/vide…
退出移动版