概述
接口主要是用于实现多态的效果,接口是一个类型,一个抽象的类型,既然是类型,就可以定义接口类型的变量。
定义
接口好像用于定义方法的,写上方法签名列表即可
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1 // 签名
方法名2( 参数列表2 ) 返回值列表2
…
}
接口名称:我们一般会在接口名称后面添加 er
。
方法签名:
- 当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问,也就是说可以被其它包所实现;
- 参数列表和返回值列表中的参数 变量名可以省略
type sayer interface {
Say() string
}
结构体变量赋值
如果一个变量定义为接口类型的变量,那么如果一个结构体实现了这个接口,并且方法的接收者都是值类型的,那么这个结构体的值类型和指针类型都可以赋予给这个接口变量。
例如下例中,结构体 dog,实现了 Mover 接口的 move 和 eat 方法,并且这个两个方法的接受者都是值类型,所以 dog{}
可以赋值给这个 Mover 接口变量,&dog{}
也可以赋值给这个 Mover 接口变量
package main
import "fmt"
type Mover interface {
move()
eat()
}
// dog 类型的方法的接受者都是值类型
type dog struct{}
func (d dog) move() {
fmt.Println("狗会动")
}
func (d dog) eat() {
fmt.Println("吃")
}
func main() {
var x Mover
x = &dog{} // 指针类型可以赋值
x.move()
var y Mover
y = dog{} // 值类型可以赋值
y.move()
}
但是,如果结构体方法的接收者存在指针类型,那么只能这个结构体的指针类型都可以赋予给这个接口变量,值类型不行,会报错。
如下所示 move 方法是指针类型,所以只能 &dog{}
赋予给 Mover
接口变量,用 dog{}
值类型赋予接口变量的话,将会报错
package main
import "fmt"
type Mover interface {
move()
eat()
}
type dog struct{}
// move 方法的接受者是指针类型*dog
func (d *dog) move() {
fmt.Println("狗会动")
}
func (d dog) eat() {
fmt.Println("吃")
}
func main() {
var x Mover
x = &dog{} // 指针类型可以赋值
x.move()
var y Mover
y = dog{} // 值类型不可以赋值了,会报错!!!
y.move()
}
空接口
接口是行为规范的集合,空接口没有任务行为规范,也就是接口方法为空。因为没有任何行为规范,所以任何类型都可以属于空接口类型。所以空接口可以接收任何参数。
package main
import "fmt"
// Mover 自定义空接口
type Mover interface {
}
// dog 空接口参数arg
func dog(arg Mover) {
}
func main() {
// 空接口可以接受任何参数
dog(10)
dog("10")
// 空接口可以接受任何参数
var a Mover
a = 10
a = "10"
// Println 参数类型就是空接口
fmt.Println(a)
}
go 里其实已经自带了一个空接口类型 interface{}
,不需要我们自定义(上例的 Mover
空接口)
package main
import "fmt"
func main() {
// go里自带的空接口类型interface{}
var a interface{}
a = 10
a = "10"
// Println 参数类型就是空接口
fmt.Println(a)
}
在 go1.18
之后,官方更加建议使用 any
来替代 interface{}
,这样可以少写几个字母,没有别的差别。
package main
import "fmt"
func main() {
// go1.18之后,官方推荐用any替代interface{}
var a any
a = 10
a = "10"
// Println 参数类型就是空接口
fmt.Println(a)
}
断言
由于空接口可以存任何类型的值,有时候我们想要知道,空接口类型的值到底是什么类型,这个时候就需要对值进行断言。
package main
import "fmt"
func main() {
// 定义一个空接口x
var x interface{}
x = "Hello 沙河"
v, ok := x.(string) // 断言是否是字符串,ok为true或false,v为变量的值
fmt.Println(v, ok)
}
断言配合switch使用
anyVal.(type)
的形式必须配合switch使用
var anyVal interface{}
anyVal = "123"
switch specificTypeVal := anyVal.(type) {
case string:
fmt.Println(specificTypeVal) // 输出字符串3
}
有时候我们希望结构体必须实现某个接口,如果没有实现的话,就编译报错,启动不起来,可以这样写
package main
import (
"io"
)
type MyStruct struct{}
func (m *MyStruct) Read(p []byte) (n int, err error) {
return 0, nil
}
// Impl 或者写个方法,返回这个接口类型也可以
func (m *MyStruct) Impl() io.Reader {
return (*MyStruct)(nil)
}
// 这个两种方式,方法的接受者可以是指针,也可以是值类型
var _ io.Reader = (*MyStruct)(nil) // 把nil转成*MyStruct类型
var _ io.Reader = &MyStruct{}
type MyStructCaseTwo struct{}
func (m MyStructCaseTwo) Read(p []byte) (n int, err error) {
return 0, nil
}
// 方法的接受这必须是指针类型
var _ io.Reader = MyStructCaseTwo{}
func main() {
}
接口的嵌套
接口与接口间可以通过嵌套创造出新的接口,嵌套得到的接口的使用与普通接口一样
// Sayer 接口
type Sayer interface {
say()
}
// Mover 接口
type Mover interface {
move()
}
// 接口嵌套
type animal interface {
Sayer
Mover
}
接口的实现
接口是隐式实现的,一个对象只要全部实现了接口中的方法,那么就实现了这个接口;换句话说,接口就是一个 需要实现的方法列表。
type sayer interface {
Say() string
}
type Cat struct{}
// 实现了sayer接口
func (c Cat) Say() string {
return "喵喵喵"
}
一个类型可以实现多个接口,只需要实现每个接口里的方法即可
type Sayer interface {
say()
}
type Mover interface {
move()
}
type dog struct {
name string
}
func (d dog) say() {
fmt.Printf("%s会叫汪汪汪\n", d.name)
}
func (d dog) move() {
fmt.Printf("%s会动\n", d.name)
}
func main() {
var x Sayer
var y Mover
a := dog{name: "旺财"}
x = a
y = a
x.say()
y.move()
}
还可以多个类型实现同一个接口,这就是多态的实现,同一个接口类型,不同对象有不同的表现,如下所示,同一个 move 方法,dog 结构体实例和 car 结构体实例,执行的结果是不一样的
type Mover interface {
move()
}
type dog struct {
name string
}
type car struct {
brand string
}
func (d dog) move() {
fmt.Printf("%s会跑\n", d.name)
}
func (c car) move() {
fmt.Printf("%s速度70迈\n", c.brand)
}
func main() {
var x Mover
x = dog{name: "旺财"}
x.move()
x = car{brand: "保时捷"}
x.move()
}