接口是一种抽象类型,它仅包含一些方法签名,没有给出具体实现。
接口意味着约定。你拿到一个接口以后,你可以通过查看它包含的方法来知道它能做什么。
type Speaker interface {
Speak() string
}
上面的代码定义了一个名为Speaker
的接口,它包含一个方法。我们能从中看出来,Speak方法不需要提供参数就可以获取一个字符串,好像是要说点什么。
type Employee struct {
ID int
Name, Address string
}
type EmployeeManager struct {
Employee // 匿名成员
ManagerLevel int
}
func (e *Employee) Speak() string {
return fmt.Sprintf("[%d, %s], from %s", e.ID, e.Name, e.Address)
}
func (e *EmployeeManager) Speak() string {
return fmt.Sprintf("%s , level %d", e.Employee.Speak(), e.ManagerLevel)
}
上面的代码包含两个类型,这两个类型都实现了接口。实现接口的方式并不像有些语言一样需要明确的申明出来它implements
了哪些接口,在golang中,只需要将接口中的所有方法全部实现了,就算实现了这个接口。
接口同样可以组合其它接口,就像struct中的匿名成员一样。
var employee = Employee{
ID: 3,
Name: "fooEmployee",
Address: "beijing",
}
var manager = EmployeeManager{
Employee: Employee{
ID: 2,
Name: "fooManager",
Address: "beijing",
},
ManagerLevel: 4,
}
var speaker Speaker
// 如果写成 speaker = employee,则编译器会报错,因为方法的接收者类型是 *Employee 。
speaker = &employee
PrintSpeak(speaker)
PrintSpeak(&manager)
在使用接口时,可以显式地定义一个Speaker类型的变量,这个变量的可赋值性调用方法更加严格。在方法调用时,一个结构体的变量可以调用以*T
为接收者的方法,但在接口类型的赋值上,编译器是比较严格的。
interface{}
interface{}
这个类型,里面没有定义任何方法,也就意味着它没有做出任何承诺。看上去没啥用,其实它跟编译器的关系最好,可以赋值给它任意类型的变量。
var any interface{}
any = 1
any = "hello"
any = true
any = map[string]int{}
fmt.Println
这个函数的参数就是可变长度的interface{}
,也就意味着它的参数相传什么传什么,想传几个传几个。
func Println(a ...interface{}) (n int, err error)
可以把接口类型想象成一个结构体,它包含两部分,动态类型和动态值。所以,如果一个接口被绑定了一个类型的话,即便这个类型的变量值是nil
,但这个接口就不是nil
了。
var any interface{}
fmt.Println(any == nil) // true
var nilMap map[string]int = nil
any = nilMap
fmt.Println(nilMap == nil) // true
fmt.Println(any == nil) // false! 此时的any,已经具备了明确的动态类型,即便动态类型的变量是nil
那么,对于上面的问题,如何判断接口里面的值到底是个什么呢?答案就是使用接口断言。简单来讲,接口断言就是通过一个断言类型来求接口中对应的值,看下面的代码
if realMap, ok := any.(map[string]int); ok {
fmt.Println(realMap == nil)
}
接口断言的语法是var.(T)
,如果var
中包含T,则将T返回,ok为true。如果T是固定类型,那么第一个返回值就是它的值,而如果T还是一个接口类型,那么返回的仍然是一个接口值。
到目前为止,我们可以将一个较大的类型赋值给一个较小的接口类型,还可以通过接口断言来从较小的接口类型中尝试地拿到较大的类型,这个闭环就产生了。从结果上看,接口断言就类似类型检测和类型转换。
既然存在interface{}
这样的万能类型,可以将任何东西都赋值给它,也可以通过接口断言取出到底赋值了个什么,但如果可能的类型很多的话,代码会比较冗余,golang对此专门提供了语法支持,这种语法就叫类型分支。
switch value := any.(type) {
case nil:
fmt.Println("value is nil")
case string:
fmt.Printf("%/s", value)
case map[string]int:
fmt.Println(value)
}
需要注意的是,case nil
是当any
这个变量为nil,另外,这个神奇的value
在不同的case语句块中的类型也是动态的,比如在case string
中,value就是字符串类型,在case map[string]int
中,value就是一个字典类型。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://laozhu.blog.csdn.net/article/details/121243377
内容来源于网络,如有侵权,请联系作者删除!