什么是接口?
在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。
在GO语言中,比如说我定义了一个接口download,它内部有一个方法get,任何类型只要定义了这个get方法,就是实现了这个接口。不管类型内部怎么定义这个get方法,只要定义了,也就是说外部可以调用这个方法,这就算实现了这个接口。
这就是典型的 duck typing 的概念,这是很多支持面向对象编程的语言都会了解的一个概念。
一只在漂浮在海上充气的小黄鸭,只要它长得像鸭子,我们就可以说它就是一只鸭子。
当然它和真正的鸭子差的很多,但是这就像一个接口,比如我们规定的接口包含下面几个特点:
- 嘴巴扁扁长长的
- 全身黄黄的
- 有两个眼睛两个翅膀的
只要满足这些特点,那么它就是一只鸭子,很显然,这只小黄鸭就是一只鸭子。
总结一下:一个类型,只要定义了接口要求的方法,那么就是实现了该接口,至于内部怎么实现的,由类型本身决定。
接口的声明和定义
package main
import (
"interface/A"
"fmt"
)
//定义了一个接口Common,内有一个方法Get
type Common interface {
Get(key string) string
}
//定义了一个函数,第一个参数是接口
func getValue(c Common,key string) string{
return c.Get(key)
}
func main() {
var c Common
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
c = a
fmt.Println(getValue(c,"January"))
}
上面我们定义了一个接口,同时定义了一个使用该接口的函数,下面我们定义一个A类型
package A
//定义了一个A类型
type A struct {
Kvs map[string]string
}
//定义了一个A类型的Get方法
func (a A) Get(key string) string{
return a.Kvs[key]
}
该类型实现了接口Common所需的Get方法,因此实现了该接口。
执行结果
1月
A类型中有一个map,它定义的Get方法是给一个key返回一个value,接下来再定义一个B类型试试看。
package B
type Mystring string
func (m Mystring) Get(key string) string{
return key
}
B类型是一个Mystring,它是stirng的别名,它的Get方法是给一个key直接返回key。
func main() {
var c Common
//a := A.A{Kvs:make(map[string]string)}
//a.Kvs["January"] = "1月"
//fmt.Println(getValue(a,"January"))
b := B.Mystring("")
c = b
fmt.Println(getValue(c,"hello world"))
}
执行结果
hello world
这就起到了接口的效果。A和B类型都实现了Get接口,因此在调用接口的函数中,都可以传A和B类型。
接口的内部表示
接口内部到底是什么?其实我们可以把接口看成一个(type,value)的元组,它内部有一个type表示这个接口的实际类型,还有一个value表示该实际类型的值。
//定义了一个接口Common,内有一个方法Get
type Common interface {
Get(key string) string
}
//定义了一个函数,第一个参数是接口
func getValue(c Common,key string) string{
return c.Get(key)
}
func main() {
var c Common
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
c = a
fmt.Printf("%T %v\n",c,c)
//fmt.Println(getValue(c,"January"))
}
执行结果
A.A {map[January:1月]}
%T输出c的类型,%v输出c的值,从结果可以看到,c的类型就是A,c的值就是A类型底层的这个map。
类型选择
语法:interface.(type),该语法和switch语句混用,用于选择接口底层的类型
//定义了一个接口Common,内有一个方法Get
type Common interface {
Get(key string) string
}
//做类型选择
func choose(c Common) {
switch v := c.(type) {
case A.A:
fmt.Println(v.Kvs)
case B.Mystring:
fmt.Println(v)
}
}
func main() {
var c Common
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
b := B.Mystring("pigff")
choose(a)
choose(b)
}
执行结果
map[January:1月]
pigff
另外,比较的类型也可以是接口
//定义了一个接口Common,内有一个方法Get
type Common interface {
Get(key string) string
}
func choose(c Common) {
switch v := c.(type) {
case Common: //与一个接口类型做比较
fmt.Println(v.Get("January"))
default:
fmt.Println(v.Get("January"))
}
}
func main() {
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
b := B.Mystring("pigff")
choose(a)
choose(b)
}
执行结果
1月
January
类型断言
语法:interface.(实际类型),该语法用于判断接口底层类型是不是我们指定的实际类型
//c表示一个空接口,里面没有任何方法,因此任何类型都可以满足该接口
func assert(c interface{}) {
v := c.(int)
fmt.Println(v)
}
func main() {
assert("pigff")
}
程序会报错
panic: interface conversion: interface {} is string, not int
这里assert的函数中,我们规定了接口底层的数据类型是int,但是我们传进去的类型是string,再打印这个值时就会报错。
注意:这里有用到了一个空接口,它可以表示任何类型,所以在GO中想表示任何类型就是用一个空接口来表示的。
。但是实际上该语法还有一个返回值用于判断接口的底层的类型是不是我们想要的类型type。
//c表示一个空接口,里面没有任何方法,因此任何类型都可以满足该接口
func assert(c interface{}) {
v,ok := c.(int) //ok是一个bool类型,用于判断接口底层类型是不是我们想要的int
fmt.Println(v,ok)
}
func main() {
assert("pigff")
assert(1)
}
执行结果
0 false
1 true
如果c的底层类型就是int,那么v就是该类型的值,ok就是true
如果c的底层类型不是int,那么v就是int的零值,ok就是false,程序不会报错
指针接收者与值接收者
我们在说方法的时候,就讨论过方法对于这两个接收者的不同做法(GO方法),这里接口对于两种接收方式也有区别。
之前的例子实现方式都是值接收者,下面来看看指针接收者。之前的例子举得不好,这里换一个例子。
type print interface {
Print()
}
type student struct {
name string
age int
}
type teacher struct {
name string
age int
}
func (s student) Print(){
fmt.Println(s.name,s.age)
}
func (t *teacher) Print(){
fmt.Println(t.name,t.age)
}
func main() {
var v print
s := student{"pigff",21}
t := teacher{"Mike",40}
v = s
v.Print()
v = &s
v.Print()
//这是非法的
//v = t
//v.Print()
v = &t
v.Print()
}
这边有一个print接口,student和teacher结构体都实现了该接口,都定义了Print方法,只不过student的Print方法的接收者是一个值接收者,teacher的Print方法的接收者是一个指针接收者。
先来看一下输出结果
pigff 21
pigff 21
Mike 40
可见,对于值接收者,如果我们传入给接口的类型是一个值还是指针,它都可以进行方法的调用;
而对于指针接收者,它必须是一个指针类型,才可以进行方法的调用(注释那部分如果取消注释,程序会报如下错误)
.\main.go:38:4: cannot use t (type teacher) as type print in assignment:
teacher does not implement print (Print method has pointer receiver)
我们知道,不管是值接收者还是指针接收者,在方法中,我们传入的是一个值类型还是指针类型,编译器都能进行相应的转换。
但是,在这里,只有值接收者,不管我们传入的是指针还是方法都可以,如果传入的是指针那么会自动获取到它的值。
但是指针接收者,则必须传指针类型才可以。这是为什么?
因为我们在这里只知道值,编译器无法自动获取值对应的地址。(知道地址可以获得值,但是知道值并不能自动获取它的地址)
对于方法来说,如果接收者是指针接收者,那么如果调用者是指针或是可取到地址的值,那么都可以调用成功。
但是这里,这个值是在接口中的,也就是说,接口中存储的具体的值(接口中存的是类型和值)并不能取到地址,所以这里会报错。
所以,总结一下:
指针接收者只能以指针方式使用,而值接收者两者皆可。
实现多个接口
如果一个类型定义了多个接口的方法,那么它就是实现了多个接口
type print interface {
Print()
}
type get interface {
GetName() string
}
type student struct {
name string
age int
}
func (s student) Print(){
fmt.Println(s.name,s.age)
}
func (s student) GetName() string{
return s.name
}
func main() {
var v1 print
var v2 get
s := student{"pigff",21}
v1 = s
v1.Print()
v2 = s
fmt.Println(v2.GetName())
}
如上的student类型实现了两个接口,执行结果如下
pigff 21
pigff
接口的组合嵌套
像上面的代码。我们想有一个接口既要满足接口print,又要满足接口get,那么我们就可以把这两个接口组合成一个接口,这个新的接口就嵌套了这两个接口。
type print interface {
Print()
}
type get interface {
GetName() string
}
type getAndPrint interface {
//嵌套两个接口
print
get
//当然还可以有属于自己接口的方法,这里不举例
//.....
}
type student struct {
name string
age int
}
func (s student) Print(){
fmt.Println(s.name,s.age)
}
func (s student) GetName() string{
return s.name
}
func fun(c getAndPrint) {
c.Print()
fmt.Println(c.GetName())
}
func main() {
var v getAndPrint
s := student{"pigff",21}
v = s
fun(v)
}
执行结果
pigff 21
pigff
接口的零值
接口的零值是nil,对应的它内部的类型和值也都是nil
func main() {
var v getAndPrint
if v == nil {
fmt.Printf("%T %v\n",v,v)
}
}
执行结果
<nil> <nil>
如果一个空的接口想要调用它的方法,那么会报panic。
func main() {
var v getAndPrint
v.GetName()
}
执行结果
panic: runtime error: invalid memory address or nil pointer dereference