接口(interface)
1. 接口
Go语言中接口(interface)是一种类型,一种抽象的类型。
1.1. 接口的定义
Go语言提倡面向接口编程。每个 interface 都有一组方法,这些方法定义了接口的行为。
type 接口名 interface {
方法名1(参数列表) 返回值列表
方法名2(参数列表) 返回值列表
...
}
// 1.接口名:使用type将接口定义为自定义的类型名。Go语言接口命名习惯以`er`结尾。接口名最好要能突出该接口的类型含义。
// 2.方法名:方法名首字母大写且这个接口类型名首字母也是大写时,该方法可以被接口所在的包(package)之外的代码访问。
// 3.参数列表、返回值列表中的参数变量名可以省略。
- 接口是一个或多个方法签名的集合
- 接口只有方法声明,没有实现,没有数据字段
- 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
- 接口同样支持匿名字段方法
- 接口可以匿名嵌入其它接口,或嵌入到结构中
- 接口调用不会做receiver的自动切换
- 接口也可实现类似OOP中的多态
- 空接口可以作为任何类型数据的容器
- 一个类型可以实现多个接口
1.2. 接口的实现
任何类型的方法集中只要全部“实现”了接口中的方法,就表示它“实现”了该接口,无须在该类型上显式声明实现了哪个接口。
定义一个Sayer接口
type Sayer interface {
Speak()
}
定义一个Person类型,并实现了Speak方法,该类型实现了Sayer接口。
type Person struct {
Name string
}
func (p Person) Speak() {
fmt.Println("Hello, my name is", p.Name)
}
1.3. 接口的变量类型
接口类型变量能够存储所有实现了该接口的实例。
例如:
package main
import "fmt"
type Sayer interface {
Speak()
}
type Person struct {
Name string
}
func (p Person) Speak() {
fmt.Println("Hello, my name is", p.Name)
}
func (p Person) Run() {
fmt.Println("跑步去了...")
}
func main() {
var s Sayer
p := Person{"小强"}
s = p // 接口类型变量可以存储实现了该接口的实例
s.Speak() // Hello, my name is 小强
p.Run()
//s.Run() // 但不可使用接口外的方法,报错:s.Run undefined (type Sayer has no field or method Run)
}
1.4. 值接收者和指针接收者实现接口的区别
Go语言指针语法糖:当我们使用指针调用一个方法或访问结构体的字段时,编译器会自动解引用指针,这使得我们可以像直接访问变量一样操作指针。
结构体中,值方法集合不包括指针方法,指针方法集合包括所有方法
1.4.1. 值接收者实现接口
type Sayer interface {
Speak()
}
type Person struct {
Name string
}
func (p Person) Speak() {
fmt.Println("Hello, my name is", p.Name)
}
func main() {
var s Sayer
p := Person{"小强"}
s = p
p2 := &Person{"梦想家"}
s = p2
s.Speak()
}
从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是Person结构体类型的变量p,还是*Person结构体指针类型的变量p2,都可以赋值给该接口变量s。因为Go语言中有对指针类型变量求值的语法糖,p2内部会自动求值*p2。
1.4.2. 指针接收者实现接口
func (p *Person) Speak() {
fmt.Println("Hello, my name is", p.Name)
}
func main() {
var s Sayer
p := Person{"小强"}
// Person的方法集合,不包括指针方法,因此Person类型实例并未实现Speak方法,也就未实现Sayer接口。
s = p // 报错: cannot use p (variable of type Person) as type Sayer in assignment:
// Person does not implement Sayer (Speak method has pointer receiver)
p2 := &Person{"梦想家"}
s = p2
s.Speak()
}
此时实现Speak接口的是*Person类型,所以不能给s传入Person类型的p,此时s只能存储*Person类型的值。
2. 类型与接口的关系
一个类型可以同时实现多个接口,而接口之间彼此独立,不知道对方的实现。
一个接口的方法,不一定要由一个类型完全实现,接口的方法可以通过在类型中嵌入其它类型或结构体来实现。
// WashingMachine 洗衣机
type WashingMachine interface {
wash()
dry()
}
// 甩干器
type dryer struct{}
// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {
fmt.Println("甩一甩")
}
// 海尔洗衣机
type haier struct {
dryer //嵌入甩干器,实现接口dry方法
}
// 实现WashingMachine接口的wash()方法
func (h haier) wash() {
fmt.Println("洗刷刷")
}
3. 接口的嵌套
接口与接口间可以通过嵌套生成新的接口。嵌套得到的接口的使用与普通接口一样。
// Sayer 接口
type Sayer interface {
say()
}
// Mover 接口
type Mover interface {
move()
}
// 接口嵌套
type animal interface {
Sayer
Mover
}
4. 空接口
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口
,空接口类型的变量可以存储任意类型的变量。
func main() {
var x interface{}
s := "string"
x = s
i := 100
x = i
b := true
x = b
}
4.1. 空接口的应用
空接口作为函数的参数(或返回值),实现可以接收任意类型的函数参数(或返回值)
func foo(arg interface{}) {
fmt.Printf("type:%T, value:%v\n", arg, arg)
}
func main() {
foo(1)
foo("hello")
foo(true)
foo(nil)
foo(map[string]int{"a": 1})
}
空接口作为map的值,实现可以保存任意值的字典
func main() {
var m map[string]interface{}
m["name"] = "小强"
m["age"] = 18
m["married"] = false
fmt.Println(m)
}
使用类型断言,获取接口的值
空接口可以存储任意类型的值,获取其存储的具体的值就可以使用类型断言,其语法格式:
【参考基本类型章节:3.4 类型断言】
v, ok := x.(T)
// x:表示类型为interface{}的变量
// T:表示断言x可能的类型
// v:接口值
// ok:布尔值,true表示断言成功,false表示断言失败。可省略,省略后断言失败则产生panic。
func main() {
var x interface{}
x = "hello"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}