接口
在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 方法。