Golang | 既是接口又是类型,interface是什么神仙用法?
本文始发于个人公众号:TechFlow,原创不易,求个关注
今天是golang专题的第12篇文章,我们来继续聊聊interface的使用。
在上一篇文章当中我们介绍了面向对象的一些基本概念,以及golang当中interface和多态的实现方法。今天我们继续来介绍interface当中其他的一些方法。
万能类型interface
在Java以及其他语言当中接口是一种写法规范,而在golang当中,interface其实也是一种值,它可以像是值一样传递。并且在它的底层,它其实是一个值和类型的元组。
这里我们来看下golang官方文档当中的一个例子:
package main
import (
"fmt"
"math"
)
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
type F float64
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
在上面的代码当中定义了一个叫做describe的方法,在这个方法当中我们输出了两个值,一个是接口i对应的值,另一个是接口i的类型。
我们输出的结果如下:

可以看到接口当中既存储了对应的结构体的实例的信息,也存储了结构体的类型。因此interface可以理解成一种特殊的类型。
实际上也的确如此,我们可以把interface理解成一种万能数据类型,它可以接收任何类型的值。我们看下下面这种用法:
var a1 interface{} = 1
var a2 interface{} = "abc"
list := make([]interface{}, 0)
list = append(list, a1)
list = append(list, a2)
fmt.Println(list)
在代码当中我们创建了一个interface{}类型的slice,它可以接收任何类型的值和实例。另外我们用interface{}这个类型也可以接收任何结构体的值。这里可能会有些迷惑,其实很容易想明白。interface表示一种类型,可以接收任何实现了interface当中规定的方法的类型的值。当我们定义inteface{}的时候,其实是定义了空的interface,相当于不需要实现任何方法的空interface,所以任何类型都可以接收,这也就是它成为万能类型的原因。
我们接收当然没有问题,问题是我们怎么使用这些interface类型的值呢?
一种方法是我们可以判断一个interface的变量类型。判断的方法非常简单,我们在interface的变量后面用.(type)的方法来判断。它和map的key值判断一样,会返回一个值和bool类型的标记。我们可以通过这个标记判断这个类型是否正确。
if v, ok := a1.(int); ok {
fmt.Println(v)
}
如果类型比较多的话使用switch也是可以的:
switch v := i.(type) {
case int:
fmt.Println("int")
case string:
fmt.Println("string")
}
空值nil
interface类型的空值是nil,和Python当中的None是一个意思,表示一个指针指向空。如果我们在Java或者是其他语言当中对一个空指针调用方法,那么会触发NullPointerMethodError,也就是空指针报错。这也是我们初学者在编程当中最容易遇到的错误,往往原因是忘记了对声明进行初始化导致的。
但是在golang当中不会,即使是nil也可以调用interface的方法。举个例子:
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
func main() {
var i I
var t *T
i = t
i.M()
}
我们将t赋值给了i,问题是t并没有进行初始化,所以它是一个nil,那么我们的i也就会是一个nil。我们对nil调用M方法,在M方法当中我们打印了t的局部变量S。由于t此刻是一个nil,它并没有这个变量,所以会引发一个invalid memory address or nil pointer derefernce的错误,也就是对空指针进行寻址的错误。
要解决这个错误,其实很简单,我们可以在M方法当中对t进行判断,如果发现t是一个nil,那么我们则跳过执行的逻辑。当我们把M函数改成这样之后,就不会触发空指针的问题了。
func (t *T) M() {
if t == nil {
fmt.Println("nil")
return
}
fmt.Println(t.S)
}
nil触发异常的问题也是初学者经常遇到的问题之一,这也要求我们在实现结构体内方法的时候一定要记得判断调用的对象是否为nil,避免不必要的问题。
赋值的类型选择
我们都知道golang当中通过interface来实现多态,只要是实现了interface当中定义的函数,那么我们就可以将对应的实例赋值给这个interface类型。
这看起来没有问题,但是在实际执行的时候仍然会有一点点小小的问题。比如说我们有这样一段代码:
type Integer int
type Operation interface {
Less(b Integer) bool
Add(b Integer)
}
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
}
这段代码非常简单,我们定义了一个Operation的interface,并且实现了Integer类型的两个方法。表面上看一切正常,但是有一个细节。Less和Add这两个方法针对的类型是不同的,Less方法我们不需要修改原值,所以我们传入的是Integer的值,而Add方法,我们需要修改原值, 所以我们传入的类型是Integer的指针。
那么问题来了,这两个方法的类型不同, 我们还可以将它的值赋值给Operation这个interface吗?如果可以的话,我们应该传递的是值还是指针呢?下面代码当中的第二行和第三行究竟哪个是正确的呢?
var a Integer = 1
var b Operation = &a
var b Operation = a
答案是第二行的是正确的,原因也很简单,因为我们传入指针之后,golang的编译器会自动生成一个新的Less方法。在这个转换了类型的方法当中去调用了原本的方法,相当于做了一层中转。
func (a *Integer) Less(b Integer) bool{
return (*a).Less(b)
}
那反过来行不行呢?我们也写出代码:
func (a Integer) Add (b Integer) {
(&a).Add(b)
}
显然这样是不行的,因为函数执行之后修改的只能是Add这个方法当中a这个参数的值,而没办法修改原值。这和我们想要的不符合,所以golang没有选择这种策略。
总结
在今天的文章当中我们介绍了golang当中interface的一些高级用法,比如将它作为万能类型来接收各种格式的值。比如interface的空指针调用问题,以及interface中的两个函数接收类型不一致的问题。
也就是说在go语言当中,interface既是一种多态实现的规范,又有全能类型这样衍生的功能,这个设计的确是很惊艳的。对interface的熟练使用可以在一些问题当中大大降低我们编码的复杂度,以及运行的效率。这也是golang的原生优势之一。
相关阅读
今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。
- END -
Golang | 既是接口又是类型,interface是什么神仙用法?的更多相关文章
- Golang基础(8):go interface接口
一:接口概要 接口是一种重要的类型,他是一组确定的方法集合. 一个接口变量可以存储任何实现了接口方法的具体值.一个重要的例子就是io.Reader和io.Writer type Reader inte ...
- Golang之接口(interface)
Golang最重要的接口,,,, package main import ( "fmt" ) //interface类型默认是指针 /* 接口的实现 Golang中的接口,不需要显 ...
- golang中接口interface和struct结构类的分析
再golang中,我们要充分理解interface和struct这两种数据类型.为此,我们需要优先理解type的作用. type是golang语言中定义数据类型的唯一关键字.对于type中的匿名成员和 ...
- [golang note] 接口使用
侵入式接口 √ 在其他一些编程语言中,接口主要是作为不同组件之间的契约存在,即规定双方交互的规约. √ 对契约的实现是强制的,即必须确保用户的确实现了该接口,而实现一个接口,需要从该接口继承. √ 如 ...
- Golang的接口
当一只鸟走路像鸭子,游泳像鸭子,叫起来也像鸭子,那么我们就认为它就是鸭子. Duck typing 的理念因此比喻得名. Golang 通过 interface 实现 duck typing. Eff ...
- golang(08)接口介绍
原文链接 http://www.limerence2017.com/2019/09/12/golang13/#more 接口简介 golang 中接口是常用的数据结构,接口可以实现like的功能.什么 ...
- Golang中的error类型
Golang中的error类型 error类型本身就是一个预定义好的接口,里面定义了一个method type error interface { Error() string } 生成一个新的err ...
- golang拾遗:嵌入类型
这里是golang拾遗系列的第三篇,前两篇可以点击此处链接跳转: golang拾遗:为什么我们需要泛型 golang拾遗:指针和接口 今天我们要讨论的是golang中的嵌入类型(embedding t ...
- 深入学习golang(5)—接口
接口 概述 如果说goroutine和channel是Go并发的两大基石,那么接口是Go语言编程中数据类型的关键.在Go语言的实际编程中,几乎所有的数据结构都围绕接口展开,接口是Go语言中所有数据结构 ...
随机推荐
- Linux下显示运行时链接(运行时加载)
目录 介绍 如何加载动态库 dlopen() 第一个参数: 被加载动态库的路径 第二个参数: flag表示函数符号的解析方式 dlopen 返回值 dlsym() 参数: 返回值 符号优先级 dler ...
- Active Directory - Creating users via PowerShell
Method1: Create a user by executing the following PowerShell Script. New-ADUser -name 'Michael Jorda ...
- 集训作业 洛谷P1443 马的遍历
这个题是个搜索,而且有是最少的步数,肯定就是广搜啦,不知道为什么的同学先去学习一下广搜吧. 养成好习惯,看见最少步数就去想想广搜(只是我自己觉得) 竟然这个题可以如此顺畅的想到广搜,感觉不难啊,但还有 ...
- iview实战 : 树形组件自定义
Tree树形组件是 iview 中相对复杂的一个组件. 自定义节点内容 使用强大的 Render 函数可以自定义节点显示内容和交互,比如添加图标,按钮等. ——官方文档 但官方的 example 只有 ...
- linux : 新服务器部署项目要做的事
环境:阿里云服务器两台,一台web,一台db,系统centos7. 用户用外网访问web server ,web server 再去访问db server. 1 阿里云控制台进入系统2 SSH进入系统 ...
- UC 网盘:我又回来了
普通用户不限速下载,免费 10GB 空间,支持离线下载 这个域名非常厉害,某里挑选域名果然是值得称赞的.直接使用手机号即可注册登录,默认赠送 10GB 空间.不过目前好像没看到有电脑客户端,电脑上下载 ...
- 解决win10安装flask-mysqldb报错 Python2.7
win10上安装的pycharm,在pycharm创建的py2.7虚拟环境中安装flask-sqlalchemy 执行pip install flask-mysqldb报错 error: Micros ...
- element-ui设置级联选择器表单验证
data(){<el-form :model="ruleForm" :rules="rules" ref="ruleForm" lab ...
- springboot整合Druid(德鲁伊)配置多数据源数据库连接池
pom.xml <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-ja ...
- 华为云如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 华为云如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载ap ...