Files
cs-note/hzh/GolangStar/Go语言基础/Go语言接口.md
T

5.2 KiB
Raw Blame History

tags, create time
tags create time
go
golang
go基础语法
接口
2026-06-07 15:00

Go 语言接口

概述

Go 的接口是隐式实现——不需要 implements 关键字,只要类型拥有接口声明的所有方法,就自动实现了该接口。本文覆盖接口定义、空接口、类型断言、接口嵌套等核心概念。

正文

为什么 Go 接口是"鸭嘴兽"式的?

"如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。"

Go 接口是行为契约:不要求类型显式声明"我实现了这个接口",只要你的类型有对应的方法签名,就默认实现。这种设计让新类型可以天然适配已有接口。

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

// 任何有 Read([]byte) (int, error) 签名的类型都是 Reader
// 无需 implements 或 extends

[!question] ❓ 思考 如果一个结构体有 Read 方法但没声明过要实现 Reader 接口,它能被接收 Reader 参数的函数调用吗?(答案:能!)

接口定义与实现

type Phone interface {
    Call()
    SendMessage()
}

type Apple struct{ Name string }
func (a Apple) Call()            { fmt.Println(a.Name, "打电话") }
func (a Apple) SendMessage()     { fmt.Println(a.Name, "发短信") }

type Huawei struct{ Name string }
func (h Huawei) Call()           { fmt.Println(h.Name, "打电话") }
func (h Huawei) SendMessage()    { fmt.Println(h.Name, "发短信") }

// Apple 和 Huawei 都自动实现了 Phone 接口
var p Phone = Apple{"iPhone"}
p.Call()       // iPhone 打电话
p = Huawei{"Mate"}
p.SendMessage() // Mate 发短信

多接口实现

一个类型可以实现多个接口:

type Reader interface { Read() }
type Writer interface { Write() }

type ReadWriter struct{}
func (rw ReadWriter) Read()   {}
func (rw ReadWriter) Write()  {}
// ReadWriter 同时实现了 Reader 和 Writer

空接口 interface{}

没有方法的接口就是空接口——所有类型都实现了它。

var any interface{}
any = 42          // int
any = "hello"     // string
any = true        // bool

// fmt.Println 的参数就是 ...interface{}
fmt.Println(any, 3.14, "world")

[!info] ℹ️ Go 1.20+ 的 any 别名 any 是 interface{} 的内置别名,语义等价但更简洁:

func Process(v any) { ... }

类型断言

从空接口中取出原始值需要类型断言:

x := getValue()  // 返回 interface{}

// 方式一:带 ok 判断(推荐)
val, ok := x.(int)
if ok {
    fmt.Println("是 int:", val)
} else {
    fmt.Println("不是 int")
}

// 方式二:直接取值(失败会 panic)
val := x.(int)  // ❌ 如果 x 不是 int,程序崩溃

[!warning] ⚠️ 忘记 ok 的危险 x.(int) 在类型不匹配时会 panic。生产代码中始终使用 val, ok := x.(T) 形式。

switch 类型断言

当不确定具体类型时,用 switch + .(type) 处理多种可能:

func describe(i interface{}) {
    switch v := i.(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)
    default:
        fmt.Printf("unknown: %T\n", i)  // %T 打印动态类型
    }
}

describe(42)      // int: 42
describe("hi")    // string: hi
describe(nil)     // unknown: <nil>

[!note] 📝 nil interface vs interface(nil)

  • var x interface{} = nil → x == nil 为 true
  • var x interface{} = (*int)(nil) → x != nil(类型信息存在,只是值为 nil)

接口作函数参数

这是 Go 中最常见的接口用法——让函数接受"行为"而非"具体类型":

type Speaker interface {
    Speak() string
}

func Greet(s Speaker) {
    fmt.Println("Hello,", s.Speak())
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return d.Name }

type Human struct{ Name string }
func (h Human) Speak() string { return h.Name }

Greet(Dog{"Rex"})    // Hello, Rex
Greet(Human{"Alice"}) // Hello, Alice

[!tip] 💡 依赖倒置 函数应该依赖接口而非具体类型。这样新增类型时无需修改现有函数代码——符合开闭原则。

接口嵌套

接口可以嵌入其他接口,组合出更大的契约:

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

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

// Closer 组合了 Reader 和 Writer 的所有方法
type Closer interface {
    Reader
    Writer
    Close() error
}

// 实现 Closer 需要实现 Read/Write/Close 三个方法

[!info] ℹ️ io 包的经典嵌套 io.ReadCloser、io.WriteCloser、io.ReadWriteCloser 都是 stdlib 中嵌套接口的实际应用。

常见陷阱总结

陷阱 说明 修复
nil 接口 vs 含 nil 值的接口 (*T)(nil) 赋值给接口后不为 nil 注意区分
类型断言不加 ok 类型不匹配会 panic 始终用 v, ok := x.(T)
接口不能作为 map key 接口类型不可比较(除非底层类型可比较) 用其他字段做 key
空接口失去类型安全 取回时需要断言 Go 1.20+ 用泛型替代部分场景