引入

结构体把数据组合在一起,方法给结构体绑定行为。但如果想让不同类型的对象都能用统一方式调用,就需要接口。接口定义一组方法签名,任何实现了这些方法的类型都算实现了这个接口,不需要显式声明。

正文

定义

定义

接口定义一组方法签名,任何实现了这些方法的类型都算实现了这个接口。Go 的接口是隐式实现的,不需要 implements 关键字。接口是引用类型,零值是 nil

语法

// 定义接口
type Speaker interface {
    Speak() string
}
 
// 实现接口(只要类型有对应方法就行)
type Dog struct {
    Name string
}
 
func (d Dog) Speak() string {
    return d.Name + " says woof"
}

例子

隐式实现

type Writer interface {
    Write(data string) string
}
 
type Pen struct{}
 
func (p Pen) Write(data string) string {
    return "Pen: " + data
}
 
type Pencil struct{}
 
func (p Pencil) Write(data string) string {
    return "Pencil: " + data
}
 
func main() {
    var w Writer
    w = Pen{}
    fmt.Println(w.Write("hello")) // Pen: hello
 
    w = Pencil{}
    fmt.Println(w.Write("hello")) // Pencil: hello
}

PenPencil 都实现了 Write 方法,就自动实现了 Writer 接口,不需要任何声明

接口作为函数参数

type Shape interface {
    Area() float64
}
 
type Circle struct {
    Radius float64
}
 
func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}
 
type Rect struct {
    Width, Height float64
}
 
func (r Rect) Area() float64 {
    return r.Width * r.Height
}
 
func PrintArea(s Shape) {
    fmt.Println(s.Area())
}
 
func main() {
    PrintArea(Circle{Radius: 5}) // 78.5
    PrintArea(Rect{Width: 3, Height: 4}) // 12
}

函数参数是接口类型,任何实现了该接口的值都能传进去。

接口值

type Speaker interface {
    Speak() string
}
 
type Cat struct {
    Name string
}
 
func (c Cat) Speak() string {
    return c.Name + " says meow"
}
 
func main() {
    var s Speaker
    fmt.Println(s == nil) // true(零值是 nil)
 
    s = Cat{Name: "Kitty"}
    fmt.Println(s.Speak()) // Kitty says meow
}

接口值内部存了动态类型动态值,调用方法时自动转到具体类型的方法。

空接口 any

func describe(i any) {
    fmt.Printf("值: %v, 类型: %T\n", i, i)
}
 
func main() {
    describe(42)       // 值: 42, 类型: int
    describe("hello")  // 值: hello, 类型: string
    describe(true)     // 值: true, 类型: bool
}

anyinterface{} 的别名,没有任何方法,所以任何类型都实现了空接口。用来接收任意类型的值。

接口嵌套

type Reader interface {
    Read() string
}
 
type Writer interface {
    Write(data string)
}
 
type ReadWriter interface {
    Reader
    Writer
}

接口可以嵌入其他接口,组合成更大的接口。ReadWriter 同时要求实现 Read()Write()

接口比较

type Speaker interface {
    Speak()
}
 
type Dog struct{ Name string }
 
func (d Dog) Speak() { fmt.Println(d.Name) }
 
func main() {
    var a Speaker = Dog{"Buddy"}
    var b Speaker = Dog{"Buddy"}
    var c Speaker = Dog{"Max"}
 
    fmt.Println(a == b) // true
    fmt.Println(a == c) // false
}

接口值比较的是动态类型和动态值,两者都相等才算相等。

nil 接口调用方法会 panic

type Speaker interface {
    Speak()
}
 
var s Speaker
// s.Speak() // panic: nil pointer

接口值为 nil 时调用方法会 panic,使用前要判空。

常见写法

写法说明
type I interface{…}定义接口
func (t T) Method()隐式实现接口
var i I = T{}接口变量赋值
i.Method()通过接口调用方法
any / interface{}空接口,接收任意类型
type RW interface{ R; W }接口嵌套/组合

特点

  • 接口是隐式实现的,不需要 implements 关键字
  • 只要类型实现了接口里的所有方法,就自动实现了该接口
  • 空接口 anyinterface{})可以接收任意类型的值
  • 接口值内部存储动态类型动态值
  • 接口可以嵌套组合,小接口拼成大接口
  • 接口零值是 nil,nil 接口调用方法会 panic
  • Go 推崇小接口,一个接口一两个方法就够了

理解

接口就是”约定好的行为规范”。Go 不关心你是什么类型,只要你实现了接口要求的方法,就当你实现了这个接口。这种鸭子类型的设计让代码非常灵活,不用提前声明继承关系。空接口 any 更是能接收任何值,是做泛型编程和多态的基础。

引出

接口值里存着动态类型,有时候需要知道接口值具体是什么类型。接下来看 Go 类型断言,学习怎么从接口值中提取出具体类型和具体值。