5.2 KiB
5.2 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
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为 truevar 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+ 用泛型替代部分场景 |