Go语言中接口(interface)
是一种类型,一种抽象的类型。接口类型
是由一组方法签名定义的集合。
接口的定义格式:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
er
,表明接口类型其它类型只要实现了一个接口的所有方法,那么就实现了这个接口。
示例:
// 定义一个包含say()方法的接口
type sayer interface {
say()
}
type dog struct{}
type cat struct{}
// dog类型实现say()方法
func (d dog) say() {
fmt.Println("汪~")
}
// cat类型实现say()方法
func (c cat) say() {
fmt.Println("喵~")
}
func main() {
var a sayer
a = dog{}
a.say() // 汪~
a = cat{}
a.say() // 喵~
}
接口类型变量可以存储所有实现该接口的类型变量。上例中,接口类型变量a
既可以存储dog
类型变量,又可以存储cat
类型变量,因为dog
和cat
类型都实现了该接口的所有方法。
有了接口,所有拥有相同方法的自定义类型都可以抽象的用接口类型表示,那么在代码中,如果要对这些自定义类型变量进行相同的操作,就不需要逐个定义和操作,只需要定义一个接口类型变量和操作,就可以适用于所有实现该接口的类型变量。
示例:
type sayer interface {
say()
}
type dog struct{}
type cat struct{}
func (d dog) say() {
fmt.Println("汪~")
}
func (c *cat) say() {
fmt.Println("喵~")
}
func speak(s sayer) {
s.say()
}
func main() {
d := &dog{}
c := &cat{}
speak(d) // 汪~
speak(c) // 喵~
}
上例中的speak
函数以接口类型变量作为参数,所有实现该接口的类型变量都可以使用该函数,减少了代码的冗余。
(value, type)
。接口类型变量还未赋值时,value
和type
都是nil
。示例接上例:
var a sayer
fmt.Printf("%v, %T\n", a, a) // ,
a = &dog{}
fmt.Printf("%v, %T\n", a, a) // &{}, *main.dog
接着上例,dog
类型的say
方法使用的是值接收者,尝试分别用值类型和指针类型的dog
类型值给接口类型变量赋值,结果如下:
var a sayer
a = dog{}
a.say() // 汪~
a = &dog{}
a.say() // 汪~
可以发现,使用值接收者时,值类型和指针类型的dog
类型值都可以赋值给接口变量。这是因为Go语言在编译时会自动给指针求值。
还是上例,将cat
类型的say
方法改成使用指针接收者:
func (c *cat) say() {
fmt.Println("喵~")
}
尝试分别用值类型和指针类型的cat
类型值给接口类型变量赋值,结果如下:
a = &cat{} // 正常接收
a.say() // 喵~
a = cat{} // 不能接收
a.say()
可以发现,使用指针接收者时,只能使用指针类型的cat
类型值给接口类型变量赋值。
dog
类型和cat
类型都实现了sayer
接口的所有方法,所以它们都实现了sayer
接口。示例:
// 实现washingMachine接口需要实现wash()和dry()接口
type washingMachine interface {
wash()
dry()
}
type dryer struct{}
// dryer类型实现了dry()方法
func (d dryer) dry() {
fmt.Println("甩一甩")
}
// haier类型嵌套了匿名结构体,此时haier类型拥有了dryer类型的dry()方法
type haier struct {
dryer
}
// haier类型实现了wash()方法
func (h haier) wash() {
fmt.Println("洗刷刷")
}
func main() {
a := haier{dryer{}}
// 可以看到haier类型的变量能够直接使用dryer类型的dry()方法
a.dry() // 甩一甩
var b washingMachine
// 因为haier类型能直接使用dry()方法和wash()方法,所以其实现了washingMachine接口
b = a
b.dry() // 甩一甩
}
所以,对于接口来说,一个类型如何实现该接口需要的所有方法并不重要,只要该类型变量可以直接使用这些方法,那么这个类型就实现了该接口。
直接给例子:
type washer interface {
wash()
}
// washingMachine接口嵌套了washer接口
type washingMachine interface {
washer
dry()
}
type dryer struct{}
func (d dryer) dry() {
fmt.Println("甩一甩")
}
func (d dryer) wash() {
fmt.Println("洗刷刷")
}
func main() {
a := dryer{}
var b washingMachine
b = a // dryer类型实现了washingMachine接口
b.dry() // 甩一甩
b.wash() // 洗刷刷
}
接口可以嵌套接口,那么如果一个类型想实现这个接口,不仅要实现该接口包含的方法,也要实现嵌套接口包含的方法。比如一堆类型既实现了a
接口,又实现了b
接口,这时就可以利用接口嵌套创造新的接口c
,其嵌套了a
接口和b
接口,那么这堆类型的变量就都可以用接口c
类型的变量来存储了。
指定了零个方法的接口被称为空接口。
格式:interface {}
空接口可以保存任何类型的值。
示例:
type emptyer interface{}
func main() {
var a emptyer
a = "爱中国"
fmt.Printf("%T %v\n", a, a) // string 爱中国
a = 15
fmt.Printf("%T %v\n", a, a) // int 15
a = true
fmt.Printf("%T %v\n", a, a) // bool true
}
// show函数可以接收任意类型的参数
func show(e interface{}) {
fmt.Printf("%T %v\n", e, e)
}
func main() {
a := 15
show(a) // int 15
b := false
show(b) // bool false
c := "爱中国"
show(c) // string 爱中国
}
// m的值可以是任意类型
var m map[int]interface{}
m = make(map[int]interface{}, 3)
m[1] = 5
m[2] = true
m[3] = "爱中国"
m[4] = []int{1, 2, 3}
fmt.Println(m) // map[1:5 2:true 3:爱中国 4:[1 2 3]]
类型断言
提供了访问接口底层具体值的方式。
格式:
v := i.(T)
该语句断言接口类型变量 i
保存了具体类型 T
,并将其底层类型为 T
的值赋予变量v
。若i
并未保存 T
类型的值,该语句就会触发一个panic
。
如果不想触发panic
,需要让类型断言
返回两个值:底层值以及报告断言是否成功的布尔值。
格式:
v, ok := i.(T)
若i
保存了一个 T
,那么 v
将会是其底层值,而 ok
为 true
。
否则,ok
将为false
而v
将为 T
类型的零值,程序并不会产生panic
。
示例:
// e是空接口类型变量,让其存储一个string类型的值
var e interface{}
e = "爱中国"
v1, ok1 := e.(string) // 断言底层类型是string类型
fmt.Println(v1, ok1) // 爱中国 true
v2, ok2 := e.(int) // 断言底层类型是int类型
fmt.Println(v2, ok2) // 0 false
可以用switch
实现多次断言:
var e interface{}
e = "爱中国"
// .(type)只能用在switch中
switch v := e.(type) {
case string:
fmt.Printf("x is a string,value is %v\n", v)
case int:
fmt.Printf("x is a int is %v\n", v)
case bool:
fmt.Printf("x is a bool is %v\n", v)
default:
fmt.Println("unsupport type!")
} // x is a string,value is 爱中国
参考1
参考2