首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Go interface实现分析

本文介绍了Go语言中接口(interface)的内部实现、nil interface和nil的区别以及使用时的一些坑。

上篇文章回顾:

Elasticsearch SQL用法详解

前言

接口(interface)代表一种“约定”或“协议”,是多个方法声明的集合。允许在非显示关联情况下,组合并调用其它类型的方法。接口无需依赖类型,带来的优点就是减少调用者可视化方法,隐藏类型内部结构和具体方法实现细节。虽然接口的优点有很多,但是接口的实现是在运行期实现的,所以存在其它额外的开销。在日常开发过程中是否选择接口需要根据场景进行合理的选择。

1、接口定义

一个接口需要包括方法签名,方法签名包括:方法名称、参数和返回列表。接口内不能有字段,而且不能定义自己的方法,这主要是由于字段和方法的定义需要分配内存。

>>>>

1.1 如何确保类型实现接口

Go语言接口是隐式实现的,这意味着开发人员不需要声明它实现的接口。虽然这通常非常方便,但在某些情况下可能需要明确检查接口的实现。最好的方法就是依赖编译器实现,例如:

2、接口内部实现

接口调用是通过所属于它的方法集进行调用,而类型调用则通过它所属于的方法进行调用,它们之间有本质的差别。接下来说说接口是如何实现的,以及如何获取接口的方法集。

>>>>

2.1 接口内部实现

runtime中有两种方式对接口实现,一种是iface类型,另一种是eface。

>>>>

2.2 按值实现接口和按指针实现接口区别

2.2.1 按值实现接口

当将o实现接口Ter时,其实是将T类型内存拷贝一份,然后i.data指向新生成复制品的内存地址。当调用i.A()方法时,经过以下3个步骤:

1. 通过i.(*data)变量获取复制品内的内容。

2. 获取i.(*data).A内存。

3. 调用i.(*data).A()方法。

当调用i.B()方法时,由于receiver的是*T.B()和T.A()是不一样的,调用经过也存在区别:

1. 通过i.(*data)变量获取其内容(此时的内容指向类型T的指针)。

2. 由于i.(*data)变量获取的内容是地址,所以需要进行取地址操作。但Go内部实现禁止对该复制品进行取地址操作,所以无法调用i.B()方法。

所以代码进行编译时会报错:

T does not implement Ter (B method has pointer receiver)

2.2.2 按指针实现接口

对以上代码进行稍加改动:

此时通过调用i.A()和i.B()方法时是如何实现的呢?

1. 通过i.(*data)变量获取复制品内容(此时内容为指向类型T的指针)。

2. 获取复制品内容(即T类型地址),然后调用类型T的A和B方法。

2.2.3 接口方法集合

通过以上对接口实现分析,可以得出接口的方法集是:

1. 类型T的方法集包含所有receiver T方法。

2. 类型*T的方法集合包含所有Receiver T + *T方法。

3、nil interface和nil区别

nil interface和nil有什么区别呢?咱们可以通过两个demo来看看它们具体有什么区别。

>>>>

3.1 nil

接口内部tab和data均为空时,接口才为nil。

>>>>

3.2 nil interface

如果接口内部data值为nil,但tab不为空时,此时接口为nil interface。

>>>>

3.3 接口nil检查

可以利用reflect(反射)进行nil检查:

当然也可以通过unsafe进行检查:

4、interface性能问题

在文章刚开始就已经介绍了接口有很多优点,由于接口是在运行期实现的,所以它采用动态方法调用。相比类型直接(或静态)方法调用,性能肯定有消耗,但是这种性能的消耗不大,而主要影响是对象逃逸和无法内联。

>>>>

4.1 接口动态调用对性能影响

实例1:

反汇编:

通过以上反汇编代码可以看到接口调用方法是通过动态调用方式进行调用。

实例2:

以上代码在函数A和B内输出print,主要防止被内联之后,在main函数看不到效果。

反汇编:

通过使用接口和类型两种方式发现,接口采用动态方法调用而类型方法调用被编译器直接内联了(直接将方法调用展开在了方法调用处,减少了内存调用stack开销)。所以采用类型直接方法调用性能优于使用接口调用。

>>>>

4.2 内存逃逸

现在观察以下通过类型直接方法调用和通过接口动态方法调用编译器如何进行优化。

4.2.1 编译器对类型方法优化

4.2.2 编译器对接口方法优化

通过编译器对程序优化输出得出,当使用接口方式进行方法调用时main函数内的&t发生了逃逸。

5、总结

今天仅对接口的具体实现进行了简单分析,接口有它的优势同时也有它的缺点。在日常工程开发过程中如何选择还是需要根据具体的场景进行具体分析。希望本篇文章对大家有所帮助。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181218G0ZQFU00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券