Golang - 5.接口

Go 接口详解


一、接口基础

1.1 什么是接口

接口是一组方法签名的集合,定义了对象的行为规范。任何实现了这些方法的类型,就自动满足了该接口(鸭子类型,Duck Typing)。

type Speaker interface {
    Speak() string
}

1.2 接口的隐式实现

Go 中不需要显式声明实现了某个接口,只要类型拥有了接口的所有方法,就自动实现了该接口:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }

func (d Dog) Speak() string {
    return d.Name + ": Woof!"
}

type Cat struct{ Name string }

func (c Cat) Speak() string {
    return c.Name + ": Meow!"
}

func main() {
    var s Speaker

    s = Dog{Name: "Rex"}   // Dog 隐式实现了 Speaker
    fmt.Println(s.Speak())  // Rex: Woof!

    s = Cat{Name: "Tom"}   // Cat 隐式实现了 Speaker
    fmt.Println(s.Speak())  // Tom: Meow!
}

这就是 Go 的鸭子类型:如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。

1.3 接口零值

接口的零值是 nil

var s Speaker
fmt.Println(s == nil) // true
s.Speak()             // ❌ panic: nil pointer dereference

二、接口内部结构

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

接口值 = (类型, 值)
         ↓     ↓
      动态类型  动态值
var s Speaker
s = Dog{Name: "Rex"}

// 内部表示: (Dog, {Name: "Rex"})
//   动态类型 = Dog
//   动态值   = {Name: "Rex"}

2.1 nil 接口 vs 非 nil 接口

这是 Go 接口最容易踩的坑:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }

func (d *Dog) Speak() string {
    return d.Name + ": Woof!"
}

func getSpeaker() Speaker {
    var d *Dog  // d == nil (*Dog 类型的 nil)
    return d    // 接口值 = (*Dog, nil) ≠ nil!
}

func main() {
    s := getSpeaker()
    fmt.Println(s == nil)  // false!接口不是 nil
    fmt.Println(s.Speak()) // panic: nil pointer dereference
                           // 动态值是 nil,调用方法时解引用失败
}

关键理解

情况 接口内部 s == nil
var s Speaker (nil, nil) true
var d *Dog; s = d (*Dog, nil) false

正确做法:显式返回 nil

func getSpeaker() Speaker {
    var d *Dog
    if d == nil {
        return nil // 返回真正的 nil 接口
    }
    return d
}

三、接口组合

3.1 嵌入多个接口

接口可以嵌入其他接口,形成更大的接口:

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

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter 组合了 Reader 和 Writer
type ReadWriter interface {
    Reader
    Writer
}

任何同时实现了 ReadWrite 方法的类型,就自动实现了 ReadWriter

3.2 标准库中的接口组合

// io.ReadWriter 的定义
type ReadWriter interface {
    Reader
    Writer
}

// io.ReadWriteCloser 的定义
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

四、空接口 interface{} / any

4.1 定义

没有方法签名的接口叫空接口,任何类型都满足空接口:

// Go 1.18+ 中 any 是 interface{} 的别名
var x any // 等价于 var x interface{}

x = 42
x = "hello"
x = []int{1, 2, 3}
x = Person{Name: "Alice"}

4.2 常见用途

// 1. 接收任意类型参数
func Print(v any) {
    fmt.Println(v)
}

// 2. 存储异构数据
data := map[string]any{
    "name":   "Alice",
    "age":    30,
    "scores": []int{90, 85, 92},
}

// 3. JSON 反序列化未知结构
var result map[string]any
json.Unmarshal([]byte(`{"key":"value"}`), &result)

4.3 类型断言与类型开关

从空接口中取回具体类型,需要类型断言

var x any = "hello"

// 类型断言
s := x.(string) // 安全时使用
fmt.Println(s)  // hello

// 带检查的类型断言(推荐)
s, ok := x.(string)
fmt.Println(s, ok) // hello true

n, ok := x.(int)
fmt.Println(n, ok) // 0 false

// 不安全:断言失败会 panic
n = x.(int) // ❌ panic: interface conversion: interface {} is string, not int

4.4 类型开关(Type Switch)

func classify(v any) string {
    switch v.(type) {
    case int:
        return "整数"
    case string:
        return "字符串"
    case bool:
        return "布尔值"
    case []int:
        return "整数切片"
    case nil:
        return "nil"
    default:
        return "未知类型"
    }
}

也可以在 switch 中绑定变量:

func process(v any) {
    switch x := v.(type) {
    case int:
        fmt.Println(x * 2)
    case string:
        fmt.Println(len(x))
    default:
        fmt.Printf("unsupported type: %T\n", x)
    }
}

五、接口与指针接收者

5.1 核心规则

方法接收者 值类型 T 是否实现接口 指针类型 *T 是否实现接口
值接收者 func (t T) M() ✅ 是 ✅ 是
指针接收者 func (t *T) M() ❌ 否 ✅ 是

5.2 示例

type Modifier interface {
    Modify()
}

type Data struct {
    Value int
}

// 指针接收者
func (d *Data) Modify() {
    d.Value = 100
}

func main() {
    d := Data{Value: 42}

    // var m Modifier = d  // ❌ 编译错误:Data 未实现 Modifier
    var m Modifier = &d   // ✅ *Data 实现了 Modifier
    m.Modify()
    fmt.Println(d.Value)  // 100
}

5.3 为什么值类型不能实现指针接收者的接口

因为值类型调用指针方法时,Go 取地址得到的是临时变量的地址,修改会丢失。Go 编译器直接禁止这种情况赋给接口。

5.4 值接收者两种都能用

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }

func (d Dog) Speak() string { return d.Name + ": Woof!" }

func main() {
    d := Dog{Name: "Rex"}

    var s1 Speaker = d    // ✅ 值类型实现
    var s2 Speaker = &d   // ✅ 指针类型也实现(Go 自动解引用)

    fmt.Println(s1.Speak()) // Rex: Woof!
    fmt.Println(s2.Speak()) // Rex: Woof!
}

六、接口比较

6.1 可比较性

接口值可以使用 == 比较,当动态类型和动态值都相等时为 true

type Sayer interface {
    Say() string
}

type Person struct{ Name string }

func (p Person) Say() string { return p.Name }

func main() {
    p1 := Person{Name: "Alice"}
    p2 := Person{Name: "Alice"}
    p3 := Person{Name: "Bob"}

    var s1 Sayer = p1
    var s2 Sayer = p2
    var s3 Sayer = p3

    fmt.Println(s1 == s2) // true — 同类型,同值
    fmt.Println(s1 == s3) // false — 同类型,不同值
}

6.2 不同动态类型比较

type A struct{ Val int }
type B struct{ Val int }

func (a A) Say() string { return "A" }
func (b B) Say() string { return "B" }

var s1 Sayer = A{Val: 1}
var s2 Sayer = B{Val: 1}

fmt.Println(s1 == s2) // false — 不同动态类型

6.3 不可比较的动态值会 panic

如果动态值包含切片、Map、函数等不可比较类型,== 会 panic:

type Container struct {
    Data []int
}

func (c Container) Say() string { return "container" }

var s1 Sayer = Container{Data: []int{1}}
var s2 Sayer = Container{Data: []int{1}}

fmt.Println(s1 == s2) // ❌ panic: comparing uncomparable type []int

最佳实践:除非你确信动态类型可比较,否则不要用 == 比较接口。如需判断是否为 nil,可以安全使用 == nil


七、常用标准库接口

7.1 io.Reader

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

从数据源读取字节到 p,返回读取的字节数和错误:

import (
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, Go!")
    buf := make([]byte, 5)

    for {
        n, err := r.Read(buf)
        fmt.Print(string(buf[:n]))
        if err == io.EOF {
            break
        }
    }
    // 输出: Hello, Go!
}

7.2 io.Writer

type Writer interface {
    Write(p []byte) (n int, err error)
}

将字节写入目标:

import "os"

func main() {
    var w io.Writer = os.Stdout
    w.Write([]byte("Hello, World!\n"))
}

7.3 fmt.Stringer

type Stringer interface {
    String() string
}

类似 Java 的 toString()fmt.Println 等函数会自动调用:

type Point struct{ X, Y int }

func (p Point) String() string {
    return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}

func main() {
    p := Point{X: 3, Y: 4}
    fmt.Println(p)      // (3, 4)
    fmt.Sprintf("%v", p) // "(3, 4)"
}

7.4 error

type error interface {
    Error() string
}

自定义错误:

type DivideError struct {
    Dividend int
    Divisor  int
}

func (e *DivideError) Error() string {
    return fmt.Sprintf("cannot divide %d by zero", e.Dividend)
}

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, &DivideError{Dividend: a, Divisor: b}
    }
    return a / b, nil
}

7.5 sort.Interface

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

自定义排序:

import "sort"

type ByLength []string

func (s ByLength) Len() int           { return len(s) }
func (s ByLength) Less(i, j int) bool { return len(s[i]) < len(s[j]) }
func (s ByLength) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

func main() {
    fruits := []string{"peach", "banana", "kiwi"}
    sort.Sort(ByLength(fruits))
    fmt.Println(fruits) // [kiwi peach banana]
}

八、接口设计原则

8.1 接口要小

Go 的哲学是小接口,1-2 个方法最佳:

"The bigger the interface, the weaker the abstraction."
                        — Rob Pike
// ✅ 好:小接口,职责单一
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ❌ 不好:大接口,难以实现
type ReadWriteSeekCloser interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
    Seek(offset int64, whence int) (int64, error)
    Close() error
}

8.2 依赖接口,不依赖实现

// ✅ 依赖接口
func Process(r io.Reader) {
    buf := make([]byte, 1024)
    r.Read(buf)
    // ...
}

// 可以传入 *os.File、*bytes.Buffer、*strings.Reader 等

8.3 谁使用谁定义接口

不要在提供方预先定义一堆接口,而应由消费方根据需要定义:

// 提供方:只提供具体实现
type MySQLDB struct { /* ... */ }
func (db *MySQLDB) Query(sql string) []Row { /* ... */ }

// 消费方:按需定义接口
type Querier interface {
    Query(sql string) []Row
}

func GetUsers(q Querier) []User { /* ... */ }

8.4 避免返回接口

// ❌ 返回接口,限制了调用方
func NewDB() Querier {
    return &MySQLDB{}
}

// ✅ 返回具体类型,调用方可以按需定义接口
func NewDB() *MySQLDB {
    return &MySQLDB{}
}

原则Accept interfaces, return structs.(接收接口,返回结构体)


九、类型断言进阶

9.1 提取接口值的信息

var s Speaker = Dog{Name: "Rex"}

// 获取动态类型
fmt.Printf("%T\n", s)           // main.Dog

// 获取动态值
v := reflect.ValueOf(s)
fmt.Println(v.Interface())      // {Rex}

9.2 逐层断言

func deepCheck(v any) {
    // 第一层断言
    if rw, ok := v.(io.ReadWriter); ok {
        fmt.Println("实现了 ReadWriter")
        // 第二层断言
        if rc, ok := v.(io.ReadCloser); ok {
            fmt.Println("也实现了 ReadCloser", rc)
        }
    }
}

9.3 断言为接口类型

类型断言不限于具体类型,也可以断言为另一个接口:

type Stringer interface {
    String() string
}

var v any = 42

if s, ok := v.(Stringer); ok {
    fmt.Println(s.String())
} else {
    fmt.Println("not a Stringer")
}

十、接口与并发

10.1 接口值不是并发安全的

var s Speaker = Dog{Name: "Rex"}
// 多个 goroutine 同时调用或修改 s 会有数据竞争

需要加锁或使用 sync.Mutex 保护共享数据。

10.2 常见并发接口

// context.Context — 请求上下文
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}

十一、常见陷阱与 FAQ

11.1 值类型赋给需要指针接收者的接口

type Modifier interface {
    Modify()
}

type Data struct{ Value int }
func (d *Data) Modify() { d.Value = 100 }

func main() {
    d := Data{Value: 42}
    // var m Modifier = d   // ❌ 编译错误
    var m Modifier = &d     // ✅
    m.Modify()
}

11.2 nil 指针赋给接口后不是 nil

(详见第二章第 1 节,这是最常见的陷阱)

func returnsNil() Speaker {
    var d *Dog // nil
    return d   // 接口值 = (*Dog, nil) ≠ nil
}

11.3 接口变量不能直接取地址

var s Speaker = Dog{Name: "Rex"}
// _ = &s.Speak() // ❌ 不能对接口方法结果取地址

11.4 类型断言失败 vs 类型不匹配

var s Speaker = Dog{Name: "Rex"}

// 类型断言:运行时检查
_ = s.(Cat) // ❌ panic: interface conversion: main.Dog is not main.Cat

// 安全断言
if c, ok := s.(Cat); ok {
    fmt.Println(c)
} else {
    fmt.Println("not a Cat") // 输出这个
}

11.5 接口赋值后的修改

d := Dog{Name: "Rex"}
var s Speaker = d  // 值拷贝到接口中

// 修改原始值不影响接口
d.Name = "Buddy"
fmt.Println(s.Speak()) // Rex: Woof! — 接口中仍是旧值

// 用指针则共享数据
var s2 Speaker = &d
d.Name = "Buddy"
fmt.Println(s2.Speak()) // Buddy: Woof! — 接口中看到新值

十二、完整实战示例

package main

import (
    "fmt"
    "math"
)

// ========== 接口定义 ==========

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Describer interface {
    Describe() string
}

// 组合接口
type ShapeDescriber interface {
    Shape
    Describer
}

// ========== 实现 ==========

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

func (c Circle) Describe() string {
    return fmt.Sprintf("Circle(radius=%.2f)", c.Radius)
}

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

func (r Rectangle) Describe() string {
    return fmt.Sprintf("Rectangle(%.2fx%.2f)", r.Width, r.Height)
}

// ========== 使用接口的函数 ==========

func printShapeInfo(s Shape) {
    fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}

func printFullInfo(sd ShapeDescriber) {
    fmt.Printf("%s → 面积: %.2f, 周长: %.2f\n",
        sd.Describe(), sd.Area(), sd.Perimeter())
}

// 类型开关处理不同形状
func shapeName(s Shape) string {
    switch s.(type) {
    case Circle:
        return "圆形"
    case Rectangle:
        return "矩形"
    default:
        return "未知"
    }
}

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 3, Height: 4},
    }

    fmt.Println("=== 基本接口使用 ===")
    for _, s := range shapes {
        fmt.Printf("[%s] ", shapeName(s))
        printShapeInfo(s)
    }

    fmt.Println("\n=== 组合接口 ===")
    c := Circle{Radius: 5}
    r := Rectangle{Width: 3, Height: 4}

    // Circle 和 Rectangle 都实现了 ShapeDescriber
    var sd1 ShapeDescriber = c
    var sd2 ShapeDescriber = r
    printFullInfo(sd1)
    printFullInfo(sd2)

    fmt.Println("\n=== 类型断言 ===")
    // 安全断言
    if rect, ok := shapes[1].(Rectangle); ok {
        fmt.Printf("找到矩形: 宽=%.2f, 高=%.2f\n", rect.Width, rect.Height)
    }

    fmt.Println("\n=== 空接口 + 类型开关 ===")
    items := []any{42, "hello", true, Circle{Radius: 3}}
    for _, item := range items {
        switch v := item.(type) {
        case int:
            fmt.Printf("int: %d\n", v)
        case string:
            fmt.Printf("string: %s\n", v)
        case bool:
            fmt.Printf("bool: %t\n", v)
        case Shape:
            fmt.Printf("Shape: 面积=%.2f\n", v.Area())
        default:
            fmt.Printf("unknown: %T\n", v)
        }
    }
}

十三、要点速记

定义:  接口 = 方法签名集合,隐式实现,鸭子类型
结构:  接口值 = (动态类型, 动态值),nil 接口 = (nil, nil)
组合:  小接口 + 嵌入组合 = 灵活的抽象
空接口: any / interface{} 接受任意类型,用类型断言取回
断言:  v.(Type) 或 v.(type),务必使用 comma-ok 模式
接收者: 指针接收者 → 只有 *T 实现接口;值接收者 → T 和 *T 都实现
原则:  Accept interfaces, return structs
        接口要小,谁用谁定义
陷阱:  nil 指针赋给接口后 ≠ nil
        结构体零值不省 → 用指针

AI 助手 - deepseek-v4-flash

你好!有什么可以帮你的吗?

可以问我:推荐文章、搜索主题、了解博客内容

AI 生成内容仅供参考

未播放
0:00 / 0:00