引入
结构体把数据组合在一起,方法给结构体绑定行为。但如果想让不同类型的对象都能用统一方式调用,就需要接口。接口定义一组方法签名,任何实现了这些方法的类型都算实现了这个接口,不需要显式声明。
正文
定义
定义
接口定义一组方法签名,任何实现了这些方法的类型都算实现了这个接口。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
}Pen 和 Pencil 都实现了 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
}any 是 interface{} 的别名,没有任何方法,所以任何类型都实现了空接口。用来接收任意类型的值。
接口嵌套
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关键字 - 只要类型实现了接口里的所有方法,就自动实现了该接口
- 空接口
any(interface{})可以接收任意类型的值 - 接口值内部存储动态类型和动态值
- 接口可以嵌套组合,小接口拼成大接口
- 接口零值是
nil,nil 接口调用方法会 panic - Go 推崇小接口,一个接口一两个方法就够了
理解
接口就是”约定好的行为规范”。Go 不关心你是什么类型,只要你实现了接口要求的方法,就当你实现了这个接口。这种鸭子类型的设计让代码非常灵活,不用提前声明继承关系。空接口 any 更是能接收任何值,是做泛型编程和多态的基础。
引出
接口值里存着动态类型,有时候需要知道接口值具体是什么类型。接下来看 Go 类型断言,学习怎么从接口值中提取出具体类型和具体值。