第 6 章:接口与多态
jerry北京市2026年5月8日Go 19 次阅读 约 10 分钟

理解 Go 接口的隐式实现机制、空接口、类型断言,以及接口在实际开发中的应用。
6.1 接口定义与实现
Go 的接口是隐式实现的——不需要 implements 关键字:
// 定义接口
type Animal interface {
Speak() string
}
// 实现接口(只要实现了所有方法就算实现了接口)
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return d.Name + ": 汪汪"
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return c.Name + ": 喵喵"
}
// 多态
func MakeSound(a Animal) {
fmt.Println(a.Speak())
}
MakeSound(Dog{Name: "旺财"}) // 旺财: 汪汪
MakeSound(Cat{Name: "咪咪"}) // 咪咪: 喵喵
6.2 接口的内部结构
接口在底层由两部分组成:
// 非空接口 (iface)
┌──────┬───────┐
│ type │ value │
│ 类型信息 │ 数据指针 │
└──────┴───────┘
// 空接口 (eface)
┌──────┬───────┐
│ type │ value │
└──────┴───────┘
var a Animal = Dog{Name: "旺财"}
// type -> *Dog 的类型描述
// value -> Dog{Name: "旺财"} 的指针
6.3 空接口 interface{}
空接口可以存储任意类型的值:
var anything interface{}
anything = 42
anything = "hello"
anything = []int{1, 2, 3}
// Go 1.18+ 可以用 any 代替 interface{}
var x any = "hello"
// 常见用途:接收任意参数
func PrintAnything(v interface{}) {
fmt.Println(v)
}
6.4 类型断言
从接口值中提取具体类型:
var i interface{} = "hello"
// 方式一:直接断言(失败会 panic)
s := i.(string)
fmt.Println(s) // hello
// 方式二:安全断言(推荐)
s, ok := i.(string)
if ok {
fmt.Println(s)
}
// 类型 switch
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("整数: %d\n", v)
case string:
fmt.Printf("字符串: %s\n", v)
case bool:
fmt.Printf("布尔: %t\n", v)
default:
fmt.Printf("未知类型: %T\n", v)
}
}
6.5 接口组合
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 组合接口
type ReadWriter interface {
Reader
Writer
}
6.6 常用标准接口
// fmt.Stringer —— 自定义打印格式
type Stringer interface {
String() string
}
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d岁)", p.Name, p.Age)
}
fmt.Println(Person{"Alice", 30}) // Alice (30岁)
// error 接口
type error interface {
Error() string
}
// sort.Interface
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
// io.Reader / io.Writer
type Reader interface {
Read(p []byte) (n int, err error)
}
6.7 nil 接口的陷阱
type MyError struct {
Msg string
}
func (e *MyError) Error() string {
return e.Msg
}
func doSomething() error {
var err *MyError = nil
return err // 返回的是一个非 nil 的接口
}
func main() {
err := doSomething()
if err != nil {
fmt.Println("有错误") // 会执行!因为接口的 type 不为 nil
}
}
这是 Go 中最经典的陷阱之一:接口值只有在 type 和 value 都为 nil 时才等于 nil。
正确做法:
func doSomething() error {
var err *MyError = nil
if err != nil {
return err
}
return nil // 直接返回 nil
}
6.8 接口设计原则
// 好的接口:小而精
type Reader interface {
Read(p []byte) (n int, err error)
}
// 不好的接口:大而全
type DoEverything interface {
Read()
Write()
Close()
Flush()
Seek()
// ...
}
Go 的接口设计哲学:
- 接口应该尽量小(1-3 个方法)
- 在使用方定义接口,而不是实现方
- “Accept interfaces, return structs”(接受接口,返回结构体)
6.9 面试要点
-
Go 的接口是如何实现的?
- 隐式实现,不需要 implements 关键字
- 底层由 type 和 value 两个指针组成
-
nil 接口和持有 nil 值的接口有什么区别?
- nil 接口:type 和 value 都为 nil
- 持有 nil 值的接口:type 不为 nil,value 为 nil
- 后者
!= nil为 true
-
值接收者和指针接收者实现接口的区别?
- 值接收者:值和指针都能调用
- 指针接收者:只有指针能调用
-
空接口的性能影响?
- 使用空接口会导致装箱(boxing),有额外的内存分配
- 频繁使用空接口会增加 GC 压力
练习
- 定义一个
Shape接口(Area()和Perimeter()),用Circle和Rectangle实现 - 实现
fmt.Stringer接口,自定义一个类型的打印格式 - 编写一个函数,使用类型 switch 处理不同类型的输入
- 复现 nil 接口陷阱,并理解其原因
评论
登录 后发表评论
暂无评论