第 6 章:接口与多态

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

理解 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 面试要点

  1. Go 的接口是如何实现的?

    • 隐式实现,不需要 implements 关键字
    • 底层由 type 和 value 两个指针组成
  2. nil 接口和持有 nil 值的接口有什么区别?

    • nil 接口:type 和 value 都为 nil
    • 持有 nil 值的接口:type 不为 nil,value 为 nil
    • 后者 != nil 为 true
  3. 值接收者和指针接收者实现接口的区别?

    • 值接收者:值和指针都能调用
    • 指针接收者:只有指针能调用
  4. 空接口的性能影响?

    • 使用空接口会导致装箱(boxing),有额外的内存分配
    • 频繁使用空接口会增加 GC 压力

练习

  1. 定义一个 Shape 接口(Area()Perimeter()),用 CircleRectangle 实现
  2. 实现 fmt.Stringer 接口,自定义一个类型的打印格式
  3. 编写一个函数,使用类型 switch 处理不同类型的输入
  4. 复现 nil 接口陷阱,并理解其原因

← 上一章:指针与内存模型 | 下一章:错误处理与 panic/recover →

评论

登录 后发表评论

暂无评论