本文准备介绍interface
的一些其他方法。关于interface
的基础知识可以看一下另一篇文章:Go语言多态和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的类型。
(推荐课程:Go教程)
我们输出的结果如下:
可以看到接口当中既存储了对应的结构体的实例的信息,也存储了结构体的类型。因此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
,避免不必要的问题。
(推荐课程:Go Web编程)
赋值的类型选择
我们都知道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
没有选择这种策略。
(推荐微课:Go微课)
总结
在今天的文章当中我们介绍了golang
当中interface
的一些高级用法,比如将它作为万能类型来接收各种格式的值。比如interface
的空指针调用问题,以及interface
中的两个函数接收类型不一致的问题。
也就是说在go
语言当中,interface
既是一种多态实现的规范,又有全能类型这样衍生的功能,这个设计的确是很惊艳的。对interface
的熟练使用可以在一些问题当中大大降低我们编码的复杂度,以及运行的效率。这也是golang
的原生优势之一。希望以上的相关介绍能对大家有所帮助。
文章参考来源:www.toutiao.com/a6859567247216771587/