Go语言的接口类型详解

547 阅读4分钟

我正在参加「掘金·启航计划」

1、接口

接口(interface)是一种类型,定义了一个对象的行为规范,是一组方法的集合,接口指定了类型应该具有的方法,类型决定了如何实现这些方法。

  • 接口是一个或多个方法签名的集合
  • 任何类型的方法集中只要拥有该接口对应的全部方法签名,就表示它实现了该接口,无需在该类型上显示声明实现了哪个接口。
  • 所谓对应方法,是指有相同名称、参数列表(不包括参数名)以及返回值。该类型还可以有其他方法。
  • 接口只有方法声明,没有实现,没有数据字段。
  • 接口可以匿名嵌入其他接口,或嵌入到结构体中。
  • 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
  • 只有当接口存储的类型和对象都为nil时,接口才等于nil。
  • 接口调用不会做receiver的自动转换。
  • 接口同样支持匿名字段方法。
  • 接口也可实现类似oop中的多态。
  • 空接口可以作为任何类型数据的容器。
  • 一个类型可以实现多个接口。
  • 接口命名习惯以er结尾。

2、接口的定义

每个接口由数个方法组成,接口的定义格式如下:

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

其中:

  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如定义动物接口Animaler。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

示例代码:

package main

import "fmt"

type Animaler interface {
	Say()
}

type Dog struct {
}

type Cat struct {
}

func (d Dog) Say() {
	fmt.Println("dog say 汪 汪 汪 ~")
}

func (c Cat) Say() {
	fmt.Println("cat say 喵 喵 喵 ~")
}

func main() {
	var animal Animaler
    animal = Dog{} //值类型接收者
	animal.Say()
	animal = new(Cat)
	animal.Say()
	animal = &Dog{} //指针类型接收者
	animal.Say()
}

3、值接收者和指针接收者实现接口的区别

3.1、值接收者实现接口

type Animaler interface {
	Say()
}

type Dog struct {
}
func (d Dog) Say {
    fmt.Println("dog say 汪 汪 汪 ~")
}

func main (){
    var animal Animaler
    animal = Dog{} //值类型接收者
	animal.Say() 
	animal = &Dog{} //指针类型接收者
	animal.Say()
}

使用值接收者实现接口后,不管是dog结构体还是还是结构体指针*Dog类型的变量都可以赋值给该接口变量。

3.2、指针接收者实现接口

type Animaler interface {
	Say()
}

type Dog struct {
}
func (d *Dog) Say {
    fmt.Println("dog say 汪 汪 汪 ~")
}

func main (){
    var animal Animaler
    animal = Dog{} //值类型接收者 会报错无法编译
	animal.Say() 
	animal = &Dog{} //指针类型接收者
	animal.Say()
}

指针类型接收者实现的接口,只能存储指针类型,否则会报错。

4、类型与接口的关系

4.1、一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间相互独立,不知到对方的实现。

4.2、多个类型实现同一接口

不同类型可以实现同一接口。

4.3、接口嵌套

接口与接口可以通过嵌套创造出新的接口。

// Sayer 接口
type Sayer interface {
    say()
}

// Mover 接口
type Mover interface {
    move()
}

// 接口嵌套
type animal interface {
    Sayer
    Mover
}

5、空接口

5.1、空接口定义

空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量。

var s interface{}
s = "test"
fmt.Printf("s type:%T, s value:%v\n", s, s) //s type:string, s value:test

5.2、空接口的应用

5.2.1、空接口作为函数的参数

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

func display(s interface{}) {
    fmt.Println(s)
}

5.2.2、空接口作为map的值

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

var arr = make(map[int]interface{}, 5)
arr[0] = "test"
arr[1] = 123

5.2.3、类型断言

空接口可以存储任意类型的值,如何获取其存储的具体数据呢?

一个接口的值是由一个具体类型和具体类型的值两部分组成。这两部分分别称为接口的动态类型动态值

想要判断空接口中的值这个时候就可以使用类型断言,语法如下:

value, ok := x.(Type)
  • x表示类型为interface{}的变量
  • Type表示断言x可能是的类型

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

示例:

func main() {
    var x interface{}
    x = "test"
    v, ok := x.(string)
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("类型断言失败")
    }
}

如果断言多次需要写多个if判断,可以配合switch使用:

var s interface{}
s = "test"
switch v := s.(type) {
    case string:
    fmt.Printf("s is string, value is:%v\n", v)
    case int:
    fmt.Printf("s is int, value is:%d\n", v)
    default:
    fmt.Println("断言失败")
}