跳到主要内容

接口

在Go语言中,接口(Interface)是一个重要的概念,用于定义一组方法的集合,任何实现了这些方法的类型都可以被视为该接口的实现。接口提供了一种灵活的方式来实现多态和抽象。

接口的基本概念

定义接口

接口通过 interface 关键字定义,接口中包含方法签名,但不包含具体的实现。

type 接口类型名 interface {  // Go语言的接口在命名时,一般会在单词后面添加er。
方法名1(参数列表1) 返回值列表1 // 参数列表和返回值列表中的参数变量名可以省略
方法名2(参数列表2) 返回值列表2
...
}

type Speaker interface {
Speak() string
}

实现接口

接口就是一个需要实现的方法列表,在Go语言中一个类型不需要显式声明它实现了某个接口,这要该类型实现了接口中的所有方法,它就自动实现了该接口。

type Person struct {
Name string
}

func (p Person) Speak() string {
return "My name is " + p.Name
}

在这个例子中,Person 类型实现了 Speak() 方法,因此它也实现了 Speaker 接口。

使用接口

接口类型变量

接口类型的变量可以持有任何实现了该接口的值。

var s Speaker
s = Person{Name: "Alice"}
fmt.Println(s.Speak()) // My name is Alice

空接口

空接口 interface{} 不包含任何方法,因此任何类型都实现了空接口。空接口通常用于存储任意类型的值。

package main

import "fmt"

type Person struct{}

func main() {
var anyType interface{} // 使用空接口时不用type关键字声明,直接通过变量声明

anyType = "hello"
fmt.Printf("type: %T value: %v\n", anyType, anyType) // type: string value: hello

anyType = 123
fmt.Printf("type: %T value: %v\n", anyType, anyType) // type: int value: 123

anyType = false
fmt.Printf("type: %T value: %v\n", anyType, anyType) // type: bool value: false

anyType = Person{}
fmt.Printf("type: %T value: %v\n", anyType, anyType) // type: main.Person value: {}
}

空接口作为函数的参数

使用空接口实现可以借口任意的函数参数

func showAll(i interface{}) {
fmt.Printf("type: %T value: %v\n", i, i)
}

空接口作为map的值

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

var mapInfo = make(map[string]interface{})
mapInfo["name"] = "Alice"
mapInfo["age"] = 18
mapInfo["city"] = "Shanghai"
fmt.Println(mapInfo) // map[age:18 city:Shanghai name:Alice]

...any

any 是Go1.18后 interface{} 的别名。... 表示可变参数,即可以接收多个该类型的参数。

...any 表示可以接收任意数量的参数,这些参数可以说任何类型

package main

import (
"fmt"
)

func printAll(values ...any) { // values 是一个 []any 类型的切片
for _, value := range values {
fmt.Println(value)
}
}

func main() {
printAll(1, "hello", 3.14, true)
}

接口值

由于接口类型的值可以是任意一个实现该接口的类型值,所以接口值由两部分组成:一个具体的值和一个表示该值的类型。鉴于这两部分会根据存入值的不同而发生变化,也称为接口的 动态类型 和 动态值。

type Speaker interface {
Speak() string
}

type Person struct {
Name string
}

func (p Person)Speak() string {
return "My Name is " + p.Name
}

上述有一个 Speaker 接口和一个 Person 类型,这时创建一个 Speaker 类型的变量并赋值给它一个 Person 值

var s Speaker
s = Person{Name:"Alice"}

在这里,接口值 s 的动态类型是 Person,而动态值是 Person{Name:"Alice"}。

接口值零值

接口值的零值是 nil,即接口值的动态类型和动态值都是 nil。

var s Speaker
fmt.Println(s) // nil

此时可以通过 s == nil 来判断接口值是否为空

fmt.Println(s == nil) // true

注意: 不能对一个空接口值调用任何方法,否则会产生panic。

一个接口值即使其动态值是 nil,但如果它的动态类型不为 nil,这个接口值就不等于 nil。

type MyStruct struct{}

func (m *MyStruct) Speak() string {
return "hello"
}

var s Speaker = (*MyStruct)(nil)
fmt.Println(s == nil) // false

虽然 s 的动态值是 nil,但它的动态类型是 *MyStruct,所以 s 并不等于 nil。

类型断言和类型开关

要从接口值中提取动态类型和动态值,可以使用类型断言或类型开关。

类型断言

类型断言用于将接口类型的变量转换成具体类型。

// 格式
s.(Type)
// 接口类型的变量.(断言的类型)

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

var s Speaker = Person{Name: "Alice"}
p, ok := s.(Person) // 类型断言,转换成 Person 类型
if ok {
fmt.Println(p.Name) // Alice
} else {
fmt.Println("s 类型不为 Person")
}

类型开关

类型开关允许对接口值的动态类型进行判断,并对不同类型执行不同的操作。

func describe(i interface{}) {
switch i.(type) {
case string:
fmt.Println("string:", i)
case int:
fmt.Println("int:", i)
default:
fmt.Println("unknown type")
}
}

func main() {
describe("hello") // string: hello
describe(11) // int: 42
}

接口组合

接口可以组合,即一个接口可以包含多个其他接口的方法。

type Writer interface {
Write(p []byte) (n int, err error)
}

type Reader interface {
Read(p []byte) (n int, err error)
}

type ReadWriter interface {
Writer
Reader
}

ReadWriter 接口包含了 Writer 和 Reader 接口的所有方法,因此任何实现了 ReadWriter 的类型必须同时实现 Write 和 Read 方法。