在Go语言中接口interface是一种类型,一种抽象的类型。
interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议规则,只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性数据,只关心行为方法。
为了保护你的Go语言职业生涯,请牢记接口(interface)是一类型。
接口的定义
Go语言提倡面向接口编程。
每个接口由数个方法组成,接口的定义格式如下:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
其中:
- 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
- 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
如下例:
//定义一个接口类型sayer
type sayer interface {
say()
}
//定义一个结构体类型dog
type dog struct{}
//定义另一个结构体类型cat
type cat struct{}
//为为dog类型创建方法
func (d dog) say() {
fmt.Println("汪汪汪")
}
//为cat类型创建方法
func (c cat) say() {
fmt.Println("喵喵喵")
}
func main() {
//声明dog类型变量dog1
var dog1 = dog{}
//声明cat类型变量cat1
var cat1 = cat{}
//变量dog1调用自己的方法
dog1.say()
//cat1调用自己的方法
cat1.say()
//接口实现,实例化接口变量x,类型为sayer
var x sayer
fmt.Printf("sayer类型变量x未赋值时:type:%T \n", x)
//sayer类型变量x赋值为dog1
x = dog1
fmt.Printf("sayer类型变量x赋值dog1后:type:%T \n", x)
//用接口方式调用方法say()
x.say()
//sayer类型变量x重新赋值为cat1
x = cat1
//用接口方式调用方法say()
x.say()
fmt.Printf("sayer类型变量x赋值cat1后:type:%T \n", x)
}
值接受者和指针接受者
值接收者和指针接受者实现接口的区别
从下面的代码中我们可以发现,使用值接收者实现接口之后,不管是dog
结构体还是结构体指针*dog
类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,dog指针fugui内部会自动求值*fugui
//定义mover接口
type mover interface {
//这里的方法是指针接受者,那么mo的赋值对象也必须是指针。如果是值接受者,那就无作为,可以mo的赋值对象可以是值也可以是指针。
move()
}
//定义dog类型结构体
type dog struct{}
//wangcai和fugui都可以给mover类型变量mo赋值
// //dog类型方法,值类型实现方法
// func (d dog) move() {
// fmt.Println("狗会动")
// }
//指针接受者实现的方法,只能用指针类型的富贵给接口类型mo赋值。
//dog类型方法,指针接受者实现的方法
func (d *dog) move() {
fmt.Printf("狗会叫")
}
func main() {
var mo mover
//wangcai保存的是值类型dog
var wangcai = dog{}
mo = wangcai
mo.move()
//fugui保存的是指针类型dog
var fugui = &dog{}
mo = fugui
mo.move()
}
一个类型实现多个接口
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,狗可以叫,也可以动。我们就分别定义Sayer接口和Mover接口,如下: Mover接口。
//定义结构体dog类型
type dog struct {
name string
}
//定义接口sayer
type sayer interface {
say()
}
//定义接口mover
type mover interface {
move()
}
//为dog类型实现一个say方法
func (d *dog) say() {
fmt.Printf("%s说:旺旺旺~\n", d.name)
}
//为dog类型实现一个move方法
func (d *dog) move() {
fmt.Printf("%s跑走了 \n", d.name)
}
func main() {
//初始化变量fugui为dog类型指针
var fugui = &dog{"富贵"}
//实例变量ss为sayer类型接口
var ss sayer
//实例化变量mm为mover类型接口
var mm mover
//为ss赋值
ss = fugui
//用接口方式调用方法say()
ss.say()
//为mm赋值
mm = fugui
//用接口方式调用方法move()
mm.move()
}
多个类型实现同一个接口
Go语言中不同的类型还可以实现同一接口 首先我们定义一个Mover接口,它要求必须由一个move方法。
//定义一个接口mover类型
type mover interface {
move()
}
//定义第一个结构体类型dog
type dog struct {
name string
foot string
}
//定义第二个结构体类型car
type car struct {
name string
foot string
}
//为dog类型实现一个方法
func (d *dog) move() {
fmt.Printf("%s用%s跑的比我快 \n", d.name, d.foot)
}
//为car类型实现一个方法
func (c *car) move() {
fmt.Printf("%s用%s跑的比我快 \n", c.name, c.foot)
}
func main() {
//实例化变量fugui为结构体dog类型
var fugui = &dog{"富贵", "四条腿"}
//实例化luhu为结构体car类型
var luhu = &car{"路虎", "四个轮子"}
//实例化变量mm为接口mover类型
var mm mover
//为mover类型变量赋值dog类型变量fugui
mm = fugui
//用接口方式调用方法move()
mm.move()
//同样可以为mover类型变量赋值为car类型变量luhu
mm = luhu
//用接口方式调用方法move()
mm.move()
}
接口也可以嵌套
//定义一个接口eatAndmove类型
type eatANDmove interface {
//嵌套另一个接口eater
eater
//嵌套另一个接口mover
mover
}
//定义一个接口eater类型
type eater interface {
eat()
}
//定义另一个接口mover类型
type mover interface {
move()
}
//定义结构体dog类型
type dog struct {
name string
}
//为dog类型实现方法move()
func (d *dog) move() {
fmt.Printf("%s跑的特别快 \n", d.name)
}
//为dog类型实现方法eat()
func (d *dog) eat() {
fmt.Printf("%s吃骨头不硌牙 \n", d.name)
}
func main() {
//实例化变量fugui为结构体dog类型
var fugui = &dog{"旺财"}
//实例化变量mm为接口mover类型
var mm mover
//为mover类型变量mm赋值
mm = fugui
//用接口方式调用方法move()
mm.move()
//实例化变量ee为接口eater类型
var ee eater
//为eater类型变量ee赋值
ee = fugui
//接口方式调用方法
ee.eat()
//嵌套型接口使用方法
//实例化变量eANDm为eatANDmove类型
var eANDm eatANDmove
//为eANDm赋值
eANDm = fugui
//用接口方式调用两个方法eat()和move()
eANDm.eat()
eANDm.move()
}
正常情况下结构体要实现接口签名中的所有方法,才能赋值给该接口类型的变量。但是也有一种特殊情况,看下面代码,接口类型变量em要求必须实现eat()和move()两种方法,才能接受赋值。结构体ji类型变量gongji只实现了一个eat()方法,正常情况下肯定不能给em赋值,但是gongji结构体嵌套了一个结构体dog,而dog类型实现了方法move(),所以变量gongji满足了接口类型变量em的要求,所以可以正常赋值。
//定义接口eatAndmove类型
type eatAndmove interface {
eat()
move()
}
//定义结构体ji类型
type ji struct {
name string
//嵌套一个结构体类型dog
dog
}
//定义结构体dog类型
type dog struct {
name string
}
//为dog类型实现mobe()方法
func (d dog) move() {
fmt.Printf("%s有四条腿 \n", d.name)
}
//为ji类型实现eat方法
func (j ji) eat() {
fmt.Printf("%s吃虫子", j.name)
}
func main() {
//实例化变量em为接口eatAndmove类型
var em eatAndmove
// var fugui = &dog{"富贵"}
//实例化变量gongji为结构体ji类型指针
var gongji = &ji{name: "大公鸡"}
//为变量em赋值gongji
em = gongji
//接口方式调用方法move()和eat()
// gongji.eat()
em.move()
em.eat()
}
空接口
空接口的定义:空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。空接口的变量可以存储任意类型的变量数据。
func main() {
//定义并实例化一个空接口,注意这里用的var关键字
var x interface{}
s := "我是字符串"
x = s
fmt.Println(x)
i := 996
x = i
fmt.Println(x)
b := true
x = b
fmt.Println(x)
//数组类型变量arr可以赋值给空接口x
arr := [...]int{2, 4, 3, 8, 9}
x = arr
fmt.Println(x)
//切片sil可以赋值给空接口x
sil := make([]string, 3, 5)
sil = []string{"我是字符串", "茅台酒", "中国电信"}
x = sil
fmt.Printf("Type:%T V:%#v \n", x, x)
//map类型变量ma可以赋值给空接口x
ma := make(map[string]bool, 3)
ma = map[string]bool{
"茅台酒": true,
"中国电信": false}
ma["字符串"] = true
x = ma
fmt.Printf("%#v \n", x)
}
函数的参数和返回值都可以是空接口,如果参数是空接口,那么该函数可以接收任意类型的参数:
//参数为空接口的函数可以结束任意类型的参数,返回值同理。
func ff(a interface{}) {
fmt.Printf("Type:%T \nValue:%#v \n", a, a)
}
func main() {
s := "我是字符串"
ff(s)
ff(976)
ff(true)
//数组类型变量arr可以赋值给空接口x
arr := [...]int{2, 4, 3, 8, 9}
ff(arr)
//切片sil可以赋值给空接口x
sil := make([]string, 3, 5)
sil = []string{"我是字符串", "茅台酒", "中国电信"}
ff(sil)
//map类型变量ma可以赋值给空接口x
ma := make(map[string]bool, 3)
ma = map[string]bool{
"茅台酒": true,
"中国电信": false}
ma["字符串"] = true
ff(ma)
}
map的值value也可以是空接口,也就是map的value是空接口的话,那么他可以保存任意类型的value值。这特么就是一个结构体了啊~~~
func main() {
ma := make(map[string]interface{}, 5)
ma = map[string]interface{}{
"中国电信": 10000,
"字符串": "我是字符串",
}
ma["布尔类型的值"] = false
fmt.Println(ma)
}
类型断言
类型断言,既然空接口可以存储任意类型的值,那么如何获取存储的具体数据类型呢?一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
- x:表示类型为interface{}的变量
- T:表示断言x可能是的类型。
- 返回值:
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。
看下面代码:
func main() {
//定义并实例化一个空接口x。注意这里用的var关键字
var x interface{}
//为x赋值
x = "aaa"
//看下面注解
v, ok := x.(bool)
fmt.Printf("v:%v \n", v)
if ok {
fmt.Printf("Type:bool Value:%v \n", v)
} else {
fmt.Printf("不是bool类型,猜错了")
}
}
注解:
把x强制转化为bool类型,如果x是bool类型,v拿到x的值,ok拿到返回值true,如果不是bool类型,ok返回false,v的返回值是你猜测类型的0值,如果你猜测是string的话v就是空字符串。这里猜测是bool类型,bool类型的0值是false。
或者也可以使用switch来判断
func main() {
var x interface{}
x = "我是字符串"
switch v := x.(type) {
case int:
fmt.Printf("Type:%T Value:%v \n", v, v)
case string:
fmt.Printf("Type:%T Value:%v \n", v, v)
case bool:
fmt.Printf("Type:%T Value:%v \n", v, v)
default:
fmt.Println("猜错了,都不是")
}
}
因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。
关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。
面试题
注意:这是一道你需要回答“能”或者“不能”的题!
首先请观察下面的这段代码,然后请回答这段代码能不能通过编译?
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "sb" {
talk = "你是个大帅比"
} else {
talk = "您好"
}
return
}
func main() {
//var peo People = &Student{}
var peo People = Student{}
think := "bitch"
fmt.Println(peo.Speak(think))
}