一、 接口的介绍

现实生活中手机、相机、U盘都可以和电脑的USB接口建立连接。我们不需要关注usb卡槽大小是否一样,因为所有的USB接口都是按照统一的标准来设计的。 Golang中的接口是一种抽象数据类型,Golang中接口定义了对象的行为规范,只定义规范不实现。接口中定义的规范由具体的对象来实现。 通俗的讲接口就一个标准,它是对一个对象的行为和规范进行约定,约定实现接口的对象必须得按照接口的规范。

在Go语言中接口(interface)是一种类型,一种抽象的类型。


interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。

为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。

二、Go接口的定义

在Golang中接口(interface)是一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,Golang中的接口不能包含任何变量。

在Golang中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了 程序设计的 多态 和 高内聚低耦合的 思想, Golang中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。

和Java一样的,实现接口所有方法就是实现了接口!
Golang中每个接口由数个方法组成,接口的定义格式如下:

  1. type 接口名 interface {
  2. 方法名1 (参数列表1) 返回值列表1
  3. 方法名2 (参数列表2) 返回值列表2
  4. }

其中

  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等,接口名最好突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名是可以省略

演示:定义一个Usber接口让Phone 和 Camera结构体实现这个接口
首先我们定义一个Usber接口,接口里面就定义了两个方法

  1. // 定义一个Usber接口
  2. type Usber interface {
  3. start()
  4. stop()
  5. }

然后我们在创建一个手机结构体

  1. // 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口
  2. // 使用结构体来实现 接口
  3. type Phone struct {
  4. Name string
  5. }
  6. // 手机要实现Usber接口的话,必须实现usb接口的所有方法
  7. func (p Phone) Start() {
  8. fmt.Println(p.Name, "启动")
  9. }
  10. func (p Phone) Stop() {
  11. fmt.Println(p.Name, "关闭")
  12. }

然后我们在创建一个Phone的结构体,来实现这个接口

  1. // 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口
  2. // 使用结构体来实现 接口
  3. type Phone struct {
  4. Name string
  5. }
  6. // 手机要实现Usber接口的话,必须实现usb接口的所有方法
  7. func (p Phone) start() {
  8. fmt.Println(p.Name, "启动")
  9. }
  10. func (p Phone) stop() {
  11. fmt.Println(p.Name, "关闭")
  12. }
  13. func main() {
  14. var phone Usber = Phone{
  15. "三星手机",
  16. }
  17. phone.start()
  18. phone.stop()
  19. }

我们在创建一个Camera结构体

  1. // 使用相机结构体来实现 接口
  2. type Camera struct {
  3. Name string
  4. }
  5. // 相机要实现Usber接口的话,必须实现usb接口的所有方法
  6. func (p Camera) start() {
  7. fmt.Println(p.Name, "启动")
  8. }
  9. func (p Camera) stop() {
  10. fmt.Println(p.Name, "关闭")
  11. }
  12. func main() {
  13. var camera Usber = Camera{
  14. "佳能",
  15. }
  16. camera.start()
  17. camera.stop()
  18. }

我们创建一个电脑的结构体,电脑的结构体就是用于接收两个实现了Usber的结构体,然后让其工作

  1. // 电脑
  2. type Computer struct {
  3. }
  4. // 接收一个实现了Usber接口的 结构体
  5. func (computer Computer) Startup(usb Usber) {
  6. usb.start()
  7. }
  8. // 关闭
  9. func (computer Computer) Shutdown (usb Usber) {
  10. usb.stop()
  11. }

最后我们在main中调用方法

  1. func main() {
  2. var camera interfaceDemo.Camera = interfaceDemo.Camera{
  3. "佳能",
  4. }
  5. var phone interfaceDemo.Phone = interfaceDemo.Phone{
  6. "苹果",
  7. }
  8. var computer interfaceDemo.Computer = interfaceDemo.Computer{}
  9. computer.Startup(camera)
  10. computer.Startup(phone)
  11. computer.Shutdown(camera)
  12. computer.Shutdown(phone)
  13. }

运行结果如下所示:

  1. 佳能 启动
  2. 苹果 启动
  3. 佳能 关闭
  4. 苹果 关闭

三、空接口(Object类型)

Golang中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。
空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。

  1. // 空接口表示没有任何约束,任意的类型都可以实现空接口
  2. type EmptyA interface {
  3. }
  4. func main() {
  5. var a EmptyA
  6. var str = "你好golang"
  7. // 让字符串实现A接口
  8. a = str
  9. fmt.Println(a)
  10. }

同时golang中空接口也可以直接当做类型来使用,可以表示任意类型。相当于Java中的Object类型

  1. var a interface{}
  2. a = 20
  3. a = "hello"
  4. a = true

空接口可以作为函数的参数,使用空接口可以接收任意类型的函数参数

  1. // 空接口作为函数参数
  2. func show(a interface{}) {
  3. fmt.println(a)
  4. }

map的值实现空接口

使用空接口实现可以保存任意值的字典

  1. // 定义一个值为空接口类型
  2. var studentInfo = make(map[string]interface{})
  3. studentInfo["userName"] = "张三"
  4. studentInfo["age"] = 15
  5. studentInfo["isWork"] = true

slice切片实现空接口

  1. // 定义一个空接口类型的切片
  2. var slice = make([]interface{}, 4, 4)
  3. slice[0] = "张三"
  4. slice[1] = 1
  5. slice[2] = true

四、类型断言

一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:

  1. x.(T)

其中:

  • X:表示类型为interface{}的变量
  • T:表示断言x可能是的类型

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败

  1. // 类型断言
  2. var a interface{}
  3. a = "132"
  4. value, isString := a.(string)
  5. if isString {
  6. fmt.Println("是String类型, 值为:", value)
  7. } else {
  8. fmt.Println("断言失败")
  9. }

或者我们可以定义一个能传入任意类型的方法

  1. // 定义一个方法,可以传入任意数据类型,然后根据不同类型实现不同的功能
  2. func Print(x interface{}) {
  3. if _,ok := x.(string); ok {
  4. fmt.Println("传入参数是string类型")
  5. } else if _, ok := x.(int); ok {
  6. fmt.Println("传入参数是int类型")
  7. } else {
  8. fmt.Println("传入其它类型")
  9. }
  10. }

上面的示例代码中,如果要断言多次,那么就需要写很多if,这个时候我们可以使用switch语句来实现:
注意:类型.(type) 只能结合switch语句使用

  1. func Print2(x interface{}) {
  2. switch x.(type) {
  3. case int:
  4. fmt.Println("int类型")
  5. case string:
  6. fmt.Println("string类型")
  7. case bool:
  8. fmt.Println("bool类型")
  9. default:
  10. fmt.Println("其它类型")
  11. }
  12. }

五、结构体接收者

值接收者

如果结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量

六、结构体实现多个接口

实现多个接口的话,可能就同时用两个接口进行结构体的接受

  1. // 定义一个Animal的接口,Animal中定义了两个方法,分别是setName 和 getName,分别让DOg结构体和Cat结构体实现
  2. type Animal interface {
  3. SetName(string)
  4. }
  5. // 接口2
  6. type Animal2 interface {
  7. GetName()string
  8. }
  9. type Dog struct {
  10. Name string
  11. }
  12. func (d *Dog) SetName(name string) {
  13. d.Name = name
  14. }
  15. func (d Dog)GetName()string {
  16. return d.Name
  17. }
  18. func main() {
  19. var dog = &Dog{
  20. "小黑",
  21. }
  22. // 同时实现两个接口
  23. var d1 Animal = dog
  24. var d2 Animal2 = dog
  25. d1.SetName("小鸡")
  26. fmt.Println(d2.GetName())
  27. }

七、接口嵌套

在golang中,允许接口嵌套接口,我们首先创建一个 Animal1 和 Animal2 接口,然后使用Animal接受刚刚的两个接口,实现接口的嵌套。

  1. // 定义一个Animal的接口,Animal中定义了两个方法,分别是setName 和 getName,分别让DOg结构体和Cat结构体实现
  2. type Animal1 interface {
  3. SetName(string)
  4. }
  5. // 接口2
  6. type Animal2 interface {
  7. GetName()string
  8. }
  9. type Animal interface {
  10. Animal1
  11. Animal2
  12. }
  13. type Dog struct {
  14. Name string
  15. }
  16. func (d *Dog) SetName(name string) {
  17. d.Name = name
  18. }
  19. func (d Dog)GetName()string {
  20. return d.Name
  21. }
  22. func main() {
  23. var dog = &Dog{
  24. "小黑",
  25. }
  26. // 同时实现两个接口
  27. var d Animal = dog
  28. d.SetName("小鸡")
  29. fmt.Println(d.GetName())
  30. }

八、Golang中空接口和类型断言

  1. // golang中空接口和类型断言
  2. var userInfo = make(map[string]interface{})
  3. userInfo["userName"] = "zhangsan"
  4. userInfo["age"] = 10
  5. userInfo["hobby"] = []string{"吃饭", "睡觉"}
  6. fmt.Println(userInfo["userName"])
  7. fmt.Println(userInfo["age"])
  8. fmt.Println(userInfo["hobby"])
  9. // 但是我们空接口如何获取数组中的值?发现 userInfo["hobby"][0] 这样做不行
  10. // fmt.Println(userInfo["hobby"][0])

也就是我们的空接口,无法直接通过索引获取数组中的内容,因此这个时候就需要使用类型断言了

  1. // 这个时候我们就可以使用类型断言了
  2. hobbyValue,ok := userInfo["hobby"].([]string)
  3. if ok {
  4. fmt.Println(hobbyValue[0])
  5. }

通过类型断言返回来的值,我们就能够直接通过角标获取了。