前置知识: Go

接口与类型断言

3 minIntermediate2026/6/14

Go接口与动态派发

概述

接口(interface)是 Go 语言实现多态的核心机制。与 Java 等语言的显式接口实现不同,Go 采用隐式实现:只要型的方法集包含了接口定义的所有方法,就自动实现了该接口型断言和型切换则提供了在运行时检查和转换接口值的能力。

基础概念

接口的内部结构

Go 的接口值在运行时由两部分组成:

  • 型信息(type):描述接口值持有的具体
  • 值信息(value):指向实际数据的指针
// 接口值的内部表示
var r io.Reader = os.Stdin
// r = (type: *os.File, value: <指向 os.Stdin 的指针>)

接口值为 nil 时,type 和 value 都是 nil。但需要注意”非空接口持有 nil 指针”的情况:

var p *int = nil
var i interface{} = p
fmt.Println(i == nil)  // false!type 不为 nil

隐式实现

Go 的接口实现是隐式的,不需要 implements 关键字:

type Reader interface {
    Read(p []byte) (n int, err error)
}

// 只要实现了 Read 方法,就自动实现了 Reader 接口
type MyReader struct{}
func (r MyReader) Read(p []byte) (int, error) { return 0, nil }

// MyReader 自动满足 Reader 接口
var r Reader = MyReader{}

接口

接口 interface{}(Go 1.18+ 可写为 any)没有任何方法约束,任何型都满足空接口

var v any     // 等价于 var v interface{}
v = 42
v = "hello"
v = []int{1, 2, 3}

快速上手

定义与实现接口

// 定义接口
type Speaker 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(s Speaker) {
    fmt.Println(s.Speak())
}

makeSound(Dog{"旺财"})  // 旺财: 汪汪
makeSound(Cat{"咪咪"})  // 咪咪: 喵喵

型断言

var r Reader = MyReader{}

// 基本断言(失败时 panic)
mr := r.(MyReader)

// 带检查的断言(推荐)
if mr, ok := r.(MyReader); ok {
    fmt.Println("是 MyReader:", mr)
}

型切换

switch v := r.(type) {
case MyReader:
    fmt.Println("MyReader:", v)
case *Buffer:
    fmt.Println("Buffer:", v)
case nil:
    fmt.Println("nil")
default:
    fmt.Printf("未知类型: %T\n", v)
}

详细用法

接口组合

Go 支持接口的组合(embedding),将多个小接口组合成大接口

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
}

// 标准库中的例子:io.ReadWriter

接口值比较

接口值可以比较,但只有当底层型可比较时才行:

var a any = 42
var b any = 42
fmt.Println(a == b)  // true

var c any = []int{1, 2}
var d any = []int{1, 2}
// fmt.Println(c == d)  // panic! 切片不可比较

nil 接口陷阱

// 常见陷阱:返回 nil 接口值
func getUser() *User {
    return nil  // 返回 nil 指针
}

func getUserInterface() interface{} {
    return getUser()  // 返回非 nil 接口(type=*User, value=nil)
}

func main() {
    v := getUserInterface()
    fmt.Println(v == nil)  // false!

    // 正确做法
    if v == nil || reflect.ValueOf(v).IsNil() {
        fmt.Println("值为 nil")
    }
}

值接收者与指针接收者

type Modifier interface {
    Modify()
}

type Data struct{ Value int }

// 值接收者:值和指针都实现接口
func (d Data) Modify() { d.Value = 0 }

// 指针接收者:只有指针实现接口
func (d *Data) Modify() { d.Value = 0 }

var m Modifier
m = Data{}    // 值接收者:OK
m = &Data{}   // 指针接收者:OK

// 指针接收者时,值类型不满足接口
// m = Data{}  // 编译错误!

接口与 nil 方法

type NopWriter struct{}

func (w *NopWriter) Write(p []byte) (int, error) {
    return len(p), nil
}

// 有时需要检查接口值是否持有 nil 指针
func safeWrite(w Writer, data []byte) error {
    if w == nil {
        return errors.New("writer is nil")
    }
    _, err := w.Write(data)
    return err
}

常见场景

场景一:策略模式

type SortStrategy interface {
    Sort(data []int)
}

type BubbleSort struct{}
func (s BubbleSort) Sort(data []int) { /* 冒泡排序 */ }

type QuickSort struct{}
func (s QuickSort) Sort(data []int) { /* 快速排序 */ }

type Sorter struct {
    strategy SortStrategy
}

func (s *Sorter) SetStrategy(strategy SortStrategy) {
    s.strategy = strategy
}

func (s *Sorter) Sort(data []int) {
    s.strategy.Sort(data)
}

场景二:中间件链

type Handler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request)
}

type Middleware func(Handler) Handler

func Chain(h Handler, middlewares ...Middleware) Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}

场景三:错误型判断

// 自定义错误类型
type NotFoundError struct {
    Resource string
}

func (e *NotFoundError) Error() string {
    return e.Resource + " 未找到"
}

func handleError(err error) {
    // 使用类型断言判断错误类型
    switch e := err.(type) {
    case *NotFoundError:
        fmt.Printf("资源未找到: %s\n", e.Resource)
    case *PermissionError:
        fmt.Printf("权限不足: %s\n", e.Action)
    default:
        fmt.Printf("未知错误: %v\n", err)
    }
}

注意事项

  • 保持接口小巧。Go 社区推崇”小接口”原则,理想情况是 1-2 个方法
  • 接口应该由消费者定义,而非生产者。在需要多态的地方定义接口,而不是预先为每个型定义接口
  • 返回具体型,接受接口型(Accept interfaces, return structs)
  • 避免返回 nil 接口值,这会导致调用方的 nil 检查失效
  • 型断言失败会 panic,始终使用带 ok 的形式
  • 接口(any)绕过了型检查,应谨慎使用,优先使用具体接口

进阶用法

接口泛型结合

// Go 1.18+ 使用泛型约束替代部分接口场景
type Number interface {
    int | int64 | float64
}

func Sum[T Number](numbers []T) T {
    var total T
    for _, n := range numbers {
        total += n
    }
    return total
}

接口嵌入与方法集

type Animal interface {
    Name() string
}

type Pet interface {
    Animal  // 嵌入 Animal 接口
    Owner() string
}

// Pet 接口需要实现 Name() 和 Owner() 两个方法

动态派发的性能

// 接口方法调用通过动态派发,比直接调用慢约 2-5 倍
// 在性能敏感的热路径中,考虑使用泛型或具体类型

// 直接调用(快)
func processDirect(d Data) { d.DoWork() }

// 接口调用(稍慢)
func processInterface(w Worker) { w.DoWork() }

// 泛型调用(快,编译时确定)
func processGeneric[T Worker](w T) { w.DoWork() }

使用接口实现 Mock 测试

// 定义接口
type EmailSender interface {
    Send(to, subject, body string) error
}

// 真实实现
type SMTPSender struct{ Host string }
func (s *SMTPSender) Send(to, subject, body string) error {
    // 发送邮件
    return nil
}

// Mock 实现
type MockSender struct {
    LastTo      string
    LastSubject string
}

func (m *MockSender) Send(to, subject, body string) error {
    m.LastTo = to
    m.LastSubject = subject
    return nil
}

// 测试
func TestNotify(t *testing.T) {
    mock := &MockSender{}
    service := NewNotificationService(mock)
    service.Notify("user@example.com", "测试")

    if mock.LastTo != "user@example.com" {
        t.Error("收件人不正确")
    }
}