- go语言中接口是一种类型,一种抽象的类型
- 接口是一种由程序员定义的类型,一个接口类型就是一组方法的集合,规定了需要实现的所有方法
- 接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计
1、接口的定义
接口就是一系列方法的集合,但是还没有实现,需要对象自己去实现
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
1、接口类型名:go语言在接口命名时,一般会在单词后面添加er,例如有写操作的接口叫Writer,关闭操作的接口叫Closer
2、方法名:当方法名首字母是大写且这个接口类型名首字母也大写时,这个方法可以被接口所在的包之外的代码访问
3、参数列表、返回值列表:参数变量名可以省略
// 定义一个接口,包含Write方法
type Writer interface{
Write([]byte) error //这个接口可以调用 Write()方法写入一个字节数组([]byte),返回值可能发生的错误(err error)
}
2、实现接口的条件
条件一:go语言中一个类型只要实现了接口中的所有方法,那么就称它实现了接口
条件二:接口方法与实现接口的类型方法格式一致,如果实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现
//定义一个Duck接口类型,包含speak和run两个方法
type Duck interface{
Speak(s string)
run()
}
//定义结构体来实现接口
type oneDuck struct{
name string
age int
}
type twoDuck struct{
name string
age int
}
//oneDuck实现speak和run方法
func(one oneDuck) Speak(s string){
fmt.Printf("鸭子会说话:我叫%s,我%d岁,我是%s\n",one.name,one.age,s)
}
func(one oneDuck) run(){
fmt.Println(one.name,"走路歪歪扭扭")
}
//twoDuck实现speak和run方法
func(two twoDuck) Speak(s string){
fmt.Printf("鸭子会说话:我叫%s,我%d岁,我是%s\n",two.name,two.age,s)
}
func(two twoDuck) run(){
fmt.Printf("%s走路好看", two.name)
}
func main(){
//实例化oneDuck
ODuck := oneDuck{"盐水鸭",12}
//实例化twoDuck
TDuck := twoDuck{"烤鸭",34}
//调用方法
ODuck.Speak("呆逼")
ODuck.run()
TDuck.Speak("傻逼")
TDuck.run()
}
哪怕只有一个方法没有实现,都不算实现接口,当接口所有方法被实现时会出现以下图标
2、接口类型变量
一个接口类型变量能存储所有实现了该接口的自定义类型变量
例如Dog和Cat类型都实现了Sayer接口,此时Sayer接口类型的变量就能够接收Dog和Cat类型的变量
var x Sayer //声明Sayer接口类型的变量x
a := Cat{} //声明Cat类型变量a
b := Dog{} //声明Dog类型变量b
x = a //可以把Cat类型的变量a赋值给x
x.Say() //变量x可以调用结构体方法
x = b
3、值接收器和指针接收器
对于实现接口来说使用值接收器和指针接收器有什么区别呢
定义一个Mover接口类型
type Mover interface{
Move()
}
3.1、值接收器实现接口
定义一个Dog结构体类型,使用值接收器为其定义一个Move方法
// Dog结构体类型
type Dog struct{}
//使用值接收定义Move方法实现Mover接口
func(d Dog) Move(){
fmt.Println("狗在叫")
}
此时实现Mover接口的是Dog类型
var x Mover //声明一个Mover类型的变量x
var d1 = Dog{} //声明一个Dog类型的变量d1
x = d1 //将d1赋值给x
x.Move()
var d2 = &Dog{} //d2是Dog的指针类型
x = d2 //也可以将d2赋值给x,因为x就是指针类型
x.Move()
上述代码中,使用值接收器实现接口后,结构体类型和结构体指针类型的变量都可以赋值给接口变量
3.2、指针接收器实现接口
//Cat结构体类型
type Cat struct{}
//使用指针接收器定义方法Move
func(c *Cat) Move(){
fmt.Println("猫在吃饭")
}
此时实现Mover接口的是*Cat类型,可以讲*Cat类型的变量直接赋值给Mover类型的变量x
var c1 = &Cat{} //c1是Cat指针类型
x = c1 //可以将c1当作Mover类型
但不能将Cat类型的变量赋值给Mover类型变量x
//以下无法编译通过
var c2 = Cat{} //c2是Cat类型
x = c2 //不能将c2当作Mover类型
4、类型与接口的关系
4.1、一个类型实现多个接口
一个自定义类型可以实现多个接口,接口间彼此独立不知道到对方的实现
// Sayer 接口
type Sayer interface {
Say()
}
// Mover 接口
type Mover interface {
Move()
}
//Dog既可以实现Sayer接口,也可以实现Mover接口
type Dog struct {
Name string
}
// 实现Sayer接口
func (d Dog) Say() {
fmt.Printf("%s会叫汪汪汪\n", d.Name)
}
// 实现Mover接口
func (d Dog) Move() {
fmt.Printf("%s会动\n", d.Name)
}
//同一个类型实现不同的接口互相不影响使用
var d = Dog{Name: "旺财"}
var s Sayer = d //d赋值给Sayer类型变量
var m Mover = d
s.Say() // 对Sayer类型调用Say方法
m.Move() // 对Mover类型调用Move方法
4.2、多种类型实现同一接口
// 实现Mover接口
func (d Dog) Move() {
fmt.Printf("%s会动\n", d.Name)
}
// Car 汽车结构体类型
type Car struct {
Brand string
}
// Move Car类型实现Mover接口
func (c Car) Move() {
fmt.Printf("%s速度70迈\n", c.Brand)
}
//这样我们在代码中就可以把狗和汽车当成一个会动的类型来处理,不必关注它们具体是什么,只需要调用它们的Move方法就可以了
var obj Mover
obj = Dog{Name: "旺财"}
obj.Move()
obj = Car{Brand: "宝马"}
obj.Move()
一个接口的所有方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现
// WashingMachine 洗衣机
type WashingMachine interface {
wash()
dry()
}
// 甩干器
type dryer struct{}
// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {
fmt.Println("甩一甩")
}
// 海尔洗衣机
type haier struct {
dryer //嵌入甩干器
}
// 实现WashingMachine接口的wash()方法
func (h haier) wash() {
fmt.Println("洗刷刷")
}
5、接口嵌套组合
- 接口与接口之间可以通过互相嵌套形成新的接口类型
- 一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用
//Go语言的 io 包中定义了写入器(Writer)、关闭器(Closer)和写入关闭器(WriteCloser)3 个接口,代码如下:
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
//接口嵌套
type WriteCloser interface {
Writer
Closer
}
//在代码中使用接口进行嵌套组合
type device struct {}
func (d *device) Write(p []byte) (int, error) {
return 0, nil
}
func (d *device) Close() error {
return nil
}
6、空接口
6.1、空接口的定义
- 空接口是指没有定义任何方法的接口类型
- 任何类型都可以视为实现了空接口
- 因为空接口类型的这个特性,空接口类型的变量可以存储任意类型的值
// Any 不包含任何方法的空接口类型
type Any interface{}
// Dog 狗结构体
type Dog struct{}
func main() {
var x Any
x = "你好" // 字符串型
fmt.Printf("type:%T value:%v\n", x, x)
x = 100 // int型
fmt.Printf("type:%T value:%v\n", x, x)
x = true // 布尔型
fmt.Printf("type:%T value:%v\n", x, x)
x = Dog{} // 结构体类型
fmt.Printf("type:%T value:%v\n", x, x)
}
通常使用空接口类型时不必使用type关键字声明:var x interface{} // 声明一个空接口类型变量x
6.2、空接口的应用
1、空接口作为函数的参数
使用空接口可以接收任意类型的函数参数
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
2、空接口作为map的值
使用空接口实现可以保存任意值的字典
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "沙河娜扎"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
7、接口值
- 接口类型的值可以是任意一个实现了该接口的类型值,所以接口值除了需要记录具体值之外,还需要记录这个值属于的类型
- 接口值由“类型”和“值”组成
//定义了一个Mover接口类型和两个实现了该接口的Dog和Car结构体类型
type Mover interface {
Move()
}
type Dog struct {
Name string
}
func (d *Dog) Move() {
fmt.Println("狗在跑~")
}
type Car struct {
Brand string
}
func (c *Car) Move() {
fmt.Println("汽车在跑~")
}
//创建一个Mover接口类型的变量m
var m Mover //此时,接口变量m是接口类型的零值,也就是它的类型和值部分都是nil
//可以使用m == nil来判断此时的接口值是否为空
fmt.Println(m == nil) // true
//不能对一个空接口值调用任何方法,否则会产生panic
m.Move() // panic: runtime error: invalid memory address or nil pointer dereference
//将一个*Dog结构体指针赋值给变量m
m = &Dog{Name: "旺财"}
此时,接口值m的动态类型会被设置为*Dog,动态值为结构体变量的拷贝
//然后,给接口变量m赋值为一个*Car类型的值
var c *Car
m = c
接口值m的动态类型为*Car,动态值为nil
此时接口变量m与nil并不相等,因为它只是动态值的部分为nil,而动态类型部分保存着对应值的类型
fmt.Println(m == nil) // false
接口值是支持相互比较的,当且仅当接口值的动态类型和动态值都相等时才相等
但是有一种特殊情况需要特别注意,如果接口值保存的动态类型相同,但是这个动态类型不支持互相比较(比如切片),那么对它们相互比较时就会引发panic
8、类型断言
接口值可能赋值为任意类型的值,那我们如何从接口值获取其存储的具体数据呢
//可以借助标准库fmt包的格式化打印获取到接口值的动态类型
var m Mover
m = &Dog{Name: "旺财"}
fmt.Printf("%T\n", m) // *main.Dog
m = new(Car)
fmt.Printf("%T\n", m) // *main.Car
想要从接口值中获取到对应的实际值需要使用类型断言,其语法格式如下:
x.(T)
x:表示接口类型的变量
T:表示断言x可能是的类型。
第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败
var n Mover = &Dog{Name: "旺财"}
v, ok := n.(*Dog)
if ok {
fmt.Println("类型断言成功")
v.Name = "富贵" // 变量v是*Dog类型
} else {
fmt.Println("类型断言失败")
}
如果对一个接口值有多个实际类型需要判断,推荐使用switch语句来实现
// justifyType 对传入的空接口类型变量x进行类型断言
func justifyType(x interface{}) {
switch v := x.(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!")
}
}