198 lines
5.2 KiB
Markdown
198 lines
5.2 KiB
Markdown
---
|
||
tags: [go, golang, go基础语法, 接口]
|
||
create time: 2026-06-07 15:00
|
||
---
|
||
|
||
# Go 语言接口
|
||
|
||
## 概述
|
||
|
||
Go 的接口是**隐式实现**——不需要 `implements` 关键字,只要类型拥有接口声明的所有方法,就自动实现了该接口。本文覆盖接口定义、空接口、类型断言、接口嵌套等核心概念。
|
||
|
||
## 正文
|
||
|
||
### 为什么 Go 接口是"鸭嘴兽"式的?
|
||
|
||
> "如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。"
|
||
|
||
Go 接口是**行为契约**:不要求类型显式声明"我实现了这个接口",只要你的类型有对应的方法签名,就默认实现。这种设计让新类型可以天然适配已有接口。
|
||
|
||
```go
|
||
type Reader interface {
|
||
Read(p []byte) (n int, err error)
|
||
}
|
||
|
||
// 任何有 Read([]byte) (int, error) 签名的类型都是 Reader
|
||
// 无需 implements 或 extends
|
||
```
|
||
|
||
> [!question] ❓ 思考
|
||
> 如果一个结构体有 `Read` 方法但没声明过要实现 `Reader` 接口,它能被接收 `Reader` 参数的函数调用吗?(答案:能!)
|
||
|
||
### 接口定义与实现
|
||
|
||
```go
|
||
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 发短信
|
||
```
|
||
|
||
#### 多接口实现
|
||
|
||
一个类型可以实现多个接口:
|
||
|
||
```go
|
||
type Reader interface { Read() }
|
||
type Writer interface { Write() }
|
||
|
||
type ReadWriter struct{}
|
||
func (rw ReadWriter) Read() {}
|
||
func (rw ReadWriter) Write() {}
|
||
// ReadWriter 同时实现了 Reader 和 Writer
|
||
```
|
||
|
||
### 空接口 `interface{}`
|
||
|
||
没有方法的接口就是空接口——**所有类型都实现了它**。
|
||
|
||
```go
|
||
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{}` 的内置别名,语义等价但更简洁:
|
||
> ```go
|
||
> func Process(v any) { ... }
|
||
> ```
|
||
|
||
### 类型断言
|
||
|
||
从空接口中取出原始值需要**类型断言**:
|
||
|
||
```go
|
||
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)` 处理多种可能:
|
||
|
||
```go
|
||
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 中最常见的接口用法——让函数接受"行为"而非"具体类型":
|
||
|
||
```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] 💡 依赖倒置
|
||
> 函数应该依赖接口而非具体类型。这样新增类型时无需修改现有函数代码——符合开闭原则。
|
||
|
||
### 接口嵌套
|
||
|
||
接口可以嵌入其他接口,组合出更大的契约:
|
||
|
||
```go
|
||
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+ 用泛型替代部分场景 |
|