37DATA

浅谈go的interface机制

一、前言

在做php重构转go的项目中,我们很多地方用到了interface类型,它在go语言中地位举足轻重,让作为静态编译语言的go语言也可以完美地支持类似php这种动态语言的特性。

二、interface使用场景

那么在go语言中,interface有哪些重要的使用场景?

1、带有方法的interface实现

这种方式可以实现多态功能,是go里面的一大行为特色。多态是一种运行期的行为,它有以下几个特点:
1)一种类型具有多种类型的能力
2)允许不同的对象对同一消息做出灵活的反应
3)以一种通用的方式对待使用的对象
4)非动态语言必须通过继承和接口的方式来实现(注:go里面没有继承,所以是通过接口来实现)

下面列举了一些实现多态的代码例子

package maintype fruits interface {   say()}type apple struct {}func (s *apple) say() {   println("I am apple!")}type pear struct {}func (s *pear) say() {   println("I am pear!")}// 主入口func main() {   // apple   apple := &apple{}   say(apple)   // pear   pear := &pear{}   say(pear)}// 以一种通用的方式对待使用的对象func say(f fruits) {   println(f)   f.say()}

执行程序,输出结果

(0x1075d58,0xc000046770)I am apple!(0x1075d78,0xc000046770)I am pear!

2、不带方法的interface实现(即空接口)

interface{} 类型是没有方法的接口,即我们说的空接口。由于没有 implements 关键字,所有类型都至少实现零个方法,并且自动满足接口,所有类型都满足空接口。因此,空接口作为参数的方法可以接受任何类型,当你不清楚方法的输入参数是什么类型时,可以用空接口类型,这里有一点动态语言的味道啦。

下面列举了函数的参数为空接口的例子,可以看出它可以接受任何数据类型。

package mainfunc main() {   var a int = 1   read(a)   var b string = "test"   read(b)   var c bool   read(c)   var d interface{}   read(d)}func read(i interface{}) {   println(i)}

执行程序,输出结果

(0x1057fe0,0xc000046758)(0x1058420,0xc000046760)(0x1057de0,0x10b3bc0)(0x0,0x0)

从上面两个例子的输出结果看,打印的接口输出中有两个指针值,下面我们再深入探究下它的内部结构是怎样的。

三、interface结构剖析

interface结构的底层结构记录在反射包中:reflect/value.go

// emptyInterface is the header for an interface{} value.type emptyInterface struct {   typ  *rtype   word unsafe.Pointer}// nonEmptyInterface is the header for an interface value with methods.type nonEmptyInterface struct {   // see ../runtime/iface.go:/Itab   itab *struct {      ityp *rtype // static interface type      typ  *rtype // dynamic concrete type      hash uint32 // copy of typ.hash      _    [4]byte      fun  [100000]unsafe.Pointer // method table   }   word unsafe.Pointer}

可以看出,空接口结构体有一个类型描述字段typ和一个包含值的字段word。

Image

展开rtype结构体如下,它包含了类型的基本描述信息

// rtype is the common implementation of most values.// It is embedded in other struct types.//// rtype must be kept in sync with ../runtime/type.go:/^type._type.type rtype struct {   size       uintptr   ptrdata    uintptr // number of bytes in the type that can contain pointers   hash       uint32  // hash of type; avoids computation in hash tables   tflag      tflag   // extra type information flags   align      uint8   // alignment of variable with this type   fieldAlign uint8   // alignment of struct field with this type   kind       uint8   // enumeration for C   // function for comparing objects of this type   // (ptr to object A, ptr to object B) -> ==?   equal     func(unsafe.Pointer, unsafe.Pointer) bool   gcdata    *byte   // garbage collection data   str       nameOff // string form   ptrToThis typeOff // type for pointer to this type, may be zero}

go里面的反射就可以通过解析这些信息确定对象的类型、获取属性信息、并且可以修改相关的属性,以及调用包含的方法等等操作的。

相比于空接口,包含方法的接口相对复杂点,但内部维护也是两个指针,tab 指向一个 itab 实体, 它表示接口的类型以及赋给这个接口的实体类型。data 则指向接口具体的值,一般而言是一个指向堆内存的指针。

Image

四、interface的注意事项

了解了interface的内部结构,我们来看看平常使用应该注意哪些?

1、直接强转获取接口其值存在风险,如果 类型错误,会出现panic,如

var a interface{} = 1// 添加断言, 类型错误,不会发生panicif val1, ok := a.(string); ok {   println(val1, ok)}// 没有加断言, 类型错误, 发生panicval2 := a.(string)println(val2)

2、接口类型包含两个字段指针,一个指向类型信息,一个指向具体数据,它们分别称为动态类型和动态值,因此判断接口值==nil,必须是动态类型和动态值都是nil的情况,举例子

package mainimport "fmt"type myError struct {}func (i *myError) Error() string {   return "MyError"}func main() {   err := Process()   fmt.Println(err)   fmt.Println(err == nil)}func Process() error {   var err *myError = nil   return err}

输出结果,这里返回false的原因就是Process返回的error虽然值是nil,但是它的类型却是MyError,所以与nil比较就是false

MyErrorfalse

3、任何从空接口内部类型的转换,都应该在原始类型转换完成后进行。这种转换为空接口,然后转换回原始类型会导致程序损耗。下面罗列了一些基准测试来说明,从结果看,与结构副本(typed 函数)相比,使用空接口需要进行双重转换(原始类型转换为空接口然后再转换回原始类型),消耗时间会多点,而且如果结构体的属性越多,耗时应该会更多。

package main_testimport "testing"var apple Appletype Apple struct {   id   int   name string   dec  string}func emptyInterface(i interface{}) {   s := i.(Apple)   apple = s}func typed(s Apple) {   apple = s}func BenchmarkWithType(b *testing.B) {   s := Apple{id: 1, name: "apple", dec: "this is apple"}   for i := 0; i < b.N; i++ {      typed(s)   }}func BenchmarkWithEmptyInterface(b *testing.B) {   s := Apple{id: 1, name: "apple", dec: "this is apple"}   for i := 0; i < b.N; i++ {      emptyInterface(s)   }}

性能测试结果如下:

Image

参考文章:
http://research.swtch.com/interfaces
https://blog.csdn.net/whatday/article/details/109775782