interface
接口类型
在Go语言中接口(interface)是一种类型,一种抽象的类型。
interfaceduck-type programminginterface
接口与鸭子类型:
维基百科的定义:
!! If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
翻译过来就是:如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。
Duck Typing,鸭子类型,是动态编程语言的一种对象推断策略,它更关注对象能如何被使用,而不是对象的类型本身。Go 语言作为一门静态语言,它通过接口的方式完美支持鸭子类型。
而在静态语言如 Java, C++ 中,必须要显示地声明实现了某个接口之后,才能用在任何需要这个接口的地方。如果你在程序中调用某个数,却传入了一个根本就没有实现另一个的类型,那在编译阶段就不会通过。这也是静态语言比动态语言更安全的原因。
动态语言和静态语言的差别在此就有所体现。静态语言在编译期间就能发现类型不匹配的错误,不像动态语言,必须要运行到那一行代码才会报错。当然,静态语言要求程序员在编码阶段就要按照规定来编写程序,为每个变量规定数据类型,这在某种程度上,加大了工作量,也加长了代码量。动态语言则没有这些要求,可以让人更专注在业务上,代码也更短,写起来更快。
Go 语言作为一门现代静态语言,是有后发优势的。它引入了动态语言的便利,同时又会进行静态语言的类型检查。Go 采用了折中的做法:不要求类型显示地声明实现了某个接口,只要实现了相关的方法即可,编译器就能检测到。
总结一下,鸭子类型是一种动态语言的风格,在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由它"当前方法和属性的集合"决定。Go 作为一种静态语言,通过接口实现了鸭子类型,实际上是 Go 的编译器在其中作了隐匿的转换工作。
Go语言的多态性:
在Java语言中,多态是通过继承和重写来体现的,而Go中的多态性就是在接口的帮助下实现的。接口可以在Go中隐式地实现。如果类型为接口中声明的所有方法提供了定义,则该类型实现了这个接口。
任何定义了接口所有方法的类型都被称为隐式地实现了该接口。
类型接口的变量可以保存实现接口的任何值。接口的这个属性用于实现Go中的多态性。
什么是接口
简言之:
- 接口是一组方法签名
- 接口把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口
接口的定义
Go语言提倡面向接口编程。
每个接口由数个方法组成,接口的定义格式如下:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
其中:
typeerWriterStringer
举个例子:
type writer interface{
Write([]byte) error
}
当你看到这个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的Write方法来做一些事情。
实现接口的条件
一个类型只要实现了接口中的全部方法,那么就实现了这个接口。换句话说,接口就是一组需要实现的方法签名。
定义一个接口并实现它:
type IPhone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type MobilePhone struct {
}
func (mobilePhone MobilePhone) call() {
fmt.Println("I am mobile, I can call you!")
}
接口的实现就是这么简单,只要实现了接口中的所有方法,就实现了这个接口。
接口类型变量
为什么要实现接口呢?
IPhoneNokiaPhoneMobilePhone
func main() {
var x IPhone // 声明一个Sayer类型的变量x
a := NokiaPhone{} // 实例化一个NokiaPhone
b := MobilePhone{} // 实例化一个MobilePhone
x = a // 可以把NokiaPhone实例直接赋值给x
x.call() // I am Nokia, I can call you!
x = b // 可以把Phone实例直接赋值给x
x.call() // I am mobile, I can call you!
}
_
// 摘自gin框架routergroup.go
type IRouter interface{ ... }
type RouterGroup struct { ... }
var _ IRouter = &RouterGroup{} // 确保RouterGroup实现了接口IRouter
值接收者和指针接收者实现接口的区别
使用值接收者实现接口和使用指针接收者实现接口有什么区别呢?
定义一个方法,有值类型接收者和指针类型接收者两种。二者都可以调用方法,因为 Go 语言编译器自动做了转换,所以值类型接收者和指针类型接收者是等价的。但是在接口的实现中,值类型接收者和指针类型接收者不一样。
type Stringer interface {
String() string
}
type person struct {
name string
age uint
addr address
}
type address struct {
province string
city string
}
func (p person) String() string{
return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}
func (addr address) String() string{
return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)
}
func printString(s fmt.Stringer){
fmt.Println(s.String())
}
func main(){
p := person{}
printString(p) //正常输出
printString(p.addr) //正常输出
printString(&p) //正常输出
}
把变量 p 的指针作为实参传给 printString 函数也是可以的,编译运行都正常。这就证明了以值类型接收者实现接口的时候,不管是类型本身,还是该类型的指针类型,都实现了该接口。
p personStringerperson*personStringer
再把接收者改成指针类型:
type Stringer interface {
String() string
}
type person struct {
name string
age uint
addr address
}
type address struct {
province string
city string
}
func (p *person) String() string{
return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}
func (addr address) String() string{
return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)
}
func printString(s fmt.Stringer){
fmt.Println(s.String())
}
func main(){
p := person{}
printString(p) //cannot use p (type person) as type fmt.Stringer in argument to printString:person does not implement fmt.Stringer (String method has pointer receiver)
printString(&p) //正常输出
}
修改成指针类型接收者后会发现,提示错误:
cannot use p (type person) as type fmt.Stringer in argument to printString:person does not implement fmt.Stringer (String method has pointer receiver)
personStringer
总结:
person*person
*person
*person
类型与接口的关系
一个类型实现多个接口
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,狗可以叫,也可以动。分别定义Sayer接口和Mover接口:
// 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)
}
func main() {
var x Sayer
var y Mover
var a = dog{name: "旺财"}
x = a
y = a
x.say()
y.move()
}
多个类型实现同一接口
Movermove
// Mover 接口
type Mover interface {
move()
}
如狗可以动,汽车也可以动,实现这个关系:
type dog struct {
name string
}
type car struct {
brand string
}
// dog类型实现Mover接口
func (d dog) move() {
fmt.Printf("%s会跑\n", d.name)
}
// car类型实现Mover接口
func (c car) move() {
fmt.Printf("%s速度70迈\n", c.brand)
}
move
func main() {
var x Mover
var a = dog{name: "旺财"}
var b = car{brand: "保时捷"}
x = a
x.move()
x = b
x.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("洗衣服")
}
接口嵌套
接口与接口间可以通过嵌套创造出新的接口。
// Sayer 接口
type Sayer interface {
say()
}
// Mover 接口
type Mover interface {
move()
}
// 接口嵌套
type animal interface {
Sayer
Mover
}
嵌套得到的接口的使用与普通接口一样。
空接口
空接口的定义
空接口是指没有定义任何方法签名的接口。由于任何类型都至少实现了0个方法,因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
type I interface{}
func main() {
var i I
i = 42 //这个时候i就是int类型
fmt.Printf("%v,%T\n", i, i)
i = "hello" //这个时候i就是string类型
fmt.Printf("%v,%T\n", i, i)
}
42,int
hello,string
空接口的应用
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
空接口作为map的值
使用空接口实现可以保存任意值的映射。
// 空接口作为map值
var person = make(map[string]interface{})
person["name"] = "张三"
person["age"] = 23
person["married"] = false
fmt.Println(person)
空接口对切片的影响
arrayslicearrayslicearrayslice
func main() {
s := []int{2, 3, 5, 7, 11, 13}
var e interface{}
e = s
f := s[0:3]
f[2] = 55
fmt.Printf("%T,%v\n", s, s)
fmt.Printf("%T,%v\n", e, e)
fmt.Printf("%T,%v\n", f, f)
}
输出
[]int,[2 3 55 7 11 13]
[]int,[2 3 55 7 11 13]
[]int,[2 3 55]
若改为
func main() {
s := []int{2, 3, 5, 7, 11, 13}
var e interface{}
e = s
g := e[1:3]
fmt.Println(g)
}
报错
cannot slice e (type interface {})
空接口的赋值
空接口可以存储任意值,但不代表任意类型就可以存储空接口类型的值
从实现的角度看,任何类型的值都满足空接口。因此空接口类型可以保存任何值,也可以从空接口中取出原值。
intstring
func main() {
// 声明a变量, 类型int, 初始值为1
var a int = 1
// 声明i变量, 类型为interface{}, 初始值为a, 此时i的值变为1
var i interface{} = a
// 声明b变量, 尝试赋值i
var b int = i
}
这个报错,它就好比可以放进行礼箱的东西,肯定能放到集装箱里,但是反过来,能放到集装箱的东西就不一定能放到行礼箱了,在 Go 里就直接禁止了这种反向操作。
cannot use i (type interface {}) as type int in assignment: need type assertion
类型断言
空接口可以存储任意类型的值,那如何获取其存储的具体数据呢?
接口值
一个具体类型具体类型的值动态类型动态值
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
请看下图分解:

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
// 安全类型断言
<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )
//非安全类型断言
<目标类型的值> := <表达式>.( 目标类型 )
示例代码:
func main() {
var i1 interface{} = new (Student)
s := i1.(Student) //不安全,如果断言失败,会直接panic
fmt.Println(s)
var i2 interface{} = new(Student)
s, ok := i2.(Student) //安全,断言失败,也不会panic,只是ok的值为false
if ok {
fmt.Println(s)
}
}
type Student struct {}
switchcasecasecasecasecase
switch ins:=s.(type) {
case Triangle:
fmt.Println("三角形。。。",ins.a,ins.b,ins.c)
case Circle:
fmt.Println("圆形。。。。",ins.radius)
case int:
fmt.Println("整型数据。。")
}
因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。
关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。