浅谈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() {// appleapple := &apple{}say(apple)// pearpear := &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 = 1read(a)var b string = "test"read(b)var c boolread(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 *rtypeword unsafe.Pointer}// nonEmptyInterface is the header for an interface value with methods.type nonEmptyInterface struct {// see ../runtime/iface.go:/Itabitab *struct {ityp *rtype // static interface typetyp *rtype // dynamic concrete typehash uint32 // copy of typ.hash_ [4]bytefun [100000]unsafe.Pointer // method table}word unsafe.Pointer}
可以看出,空接口结构体有一个类型描述字段typ和一个包含值的字段word。
展开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 uintptrptrdata uintptr // number of bytes in the type that can contain pointershash uint32 // hash of type; avoids computation in hash tablestflag tflag // extra type information flagsalign uint8 // alignment of variable with this typefieldAlign uint8 // alignment of struct field with this typekind 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) boolgcdata *byte // garbage collection datastr nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero}
go里面的反射就可以通过解析这些信息确定对象的类型、获取属性信息、并且可以修改相关的属性,以及调用包含的方法等等操作的。
相比于空接口,包含方法的接口相对复杂点,但内部维护也是两个指针,tab 指向一个 itab 实体, 它表示接口的类型以及赋给这个接口的实体类型。data 则指向接口具体的值,一般而言是一个指向堆内存的指针。
四、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 = nilreturn err}
输出结果,这里返回false的原因就是Process返回的error虽然值是nil,但是它的类型却是MyError,所以与nil比较就是false
MyErrorfalse
3、任何从空接口内部类型的转换,都应该在原始类型转换完成后进行。这种转换为空接口,然后转换回原始类型会导致程序损耗。下面罗列了一些基准测试来说明,从结果看,与结构副本(typed 函数)相比,使用空接口需要进行双重转换(原始类型转换为空接口然后再转换回原始类型),消耗时间会多点,而且如果结构体的属性越多,耗时应该会更多。
package main_testimport "testing"var apple Appletype Apple struct {id intname stringdec 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)}}
性能测试结果如下:
参考文章:
http://research.swtch.com/interfaces
https://blog.csdn.net/whatday/article/details/109775782