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

198 lines
5.2 KiB
Markdown
Raw Normal View History

2026-06-07 11:08:10 +08:00
---
2026-06-07 12:14:39 +08:00
tags: [go, golang, go基础语法, 接口]
create time: 2026-06-07 15:00
2026-06-07 11:08:10 +08:00
---
2026-06-07 12:14:39 +08:00
# Go 语言接口
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
## 概述
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
Go 的接口是**隐式实现**——不需要 `implements` 关键字,只要类型拥有接口声明的所有方法,就自动实现了该接口。本文覆盖接口定义、空接口、类型断言、接口嵌套等核心概念。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
## 正文
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 为什么 Go 接口是"鸭嘴兽"式的?
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> "如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。"
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
Go 接口是**行为契约**:不要求类型显式声明"我实现了这个接口",只要你的类型有对应的方法签名,就默认实现。这种设计让新类型可以天然适配已有接口。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```go
type Reader interface {
Read(p []byte) (n int, err error)
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
// 任何有 Read([]byte) (int, error) 签名的类型都是 Reader
// 无需 implements 或 extends
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!question] ❓ 思考
> 如果一个结构体有 `Read` 方法但没声明过要实现 `Reader` 接口,它能被接收 `Reader` 参数的函数调用吗?(答案:能!)
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 接口定义与实现
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```go
type Phone interface {
Call()
SendMessage()
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
type Apple struct{ Name string }
func (a Apple) Call() { fmt.Println(a.Name, "打电话") }
func (a Apple) SendMessage() { fmt.Println(a.Name, "发短信") }
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
type Huawei struct{ Name string }
func (h Huawei) Call() { fmt.Println(h.Name, "打电话") }
func (h Huawei) SendMessage() { fmt.Println(h.Name, "发短信") }
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
// Apple 和 Huawei 都自动实现了 Phone 接口
var p Phone = Apple{"iPhone"}
p.Call() // iPhone 打电话
p = Huawei{"Mate"}
p.SendMessage() // Mate 发短信
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
#### 多接口实现
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
一个类型可以实现多个接口:
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```go
type Reader interface { Read() }
type Writer interface { Write() }
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
type ReadWriter struct{}
func (rw ReadWriter) Read() {}
func (rw ReadWriter) Write() {}
// ReadWriter 同时实现了 Reader 和 Writer
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 空接口 `interface{}`
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
没有方法的接口就是空接口——**所有类型都实现了它**。
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
var any interface{}
any = 42 // int
any = "hello" // string
any = true // bool
// fmt.Println 的参数就是 ...interface{}
fmt.Println(any, 3.14, "world")
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!info] ℹ️ Go 1.20+ 的 `any` 别名
> `any` 是 `interface{}` 的内置别名,语义等价但更简洁:
> ```go
> func Process(v any) { ... }
> ```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 类型断言
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
从空接口中取出原始值需要**类型断言**:
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
x := getValue() // 返回 interface{}
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
// 方式一:带 ok 判断(推荐)
val, ok := x.(int)
if ok {
fmt.Println("是 int:", val)
} else {
fmt.Println("不是 int")
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
// 方式二:直接取值(失败会 panic)
val := x.(int) // ❌ 如果 x 不是 int,程序崩溃
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!warning] ⚠️ 忘记 ok 的危险
> `x.(int)` 在类型不匹配时会 **panic**。生产代码中始终使用 `val, ok := x.(T)` 形式。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
#### switch 类型断言
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
当不确定具体类型时,用 `switch` + `.(type)` 处理多种可能:
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```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 打印动态类型
}
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
describe(42) // int: 42
describe("hi") // string: hi
describe(nil) // unknown: <nil>
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!note] 📝 nil interface vs interface(nil)
> - `var x interface{} = nil` → `x == nil` 为 true
> - `var x interface{} = (*int)(nil)` → `x != nil`(类型信息存在,只是值为 nil)
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 接口作函数参数
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
这是 Go 中最常见的接口用法——让函数接受"行为"而非"具体类型":
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```go
type Speaker interface {
Speak() string
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
func Greet(s Speaker) {
fmt.Println("Hello,", s.Speak())
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
type Dog struct{ Name string }
func (d Dog) Speak() string { return d.Name }
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
type Human struct{ Name string }
func (h Human) Speak() string { return h.Name }
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
Greet(Dog{"Rex"}) // Hello, Rex
Greet(Human{"Alice"}) // Hello, Alice
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!tip] 💡 依赖倒置
> 函数应该依赖接口而非具体类型。这样新增类型时无需修改现有函数代码——符合开闭原则。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 接口嵌套
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
接口可以嵌入其他接口,组合出更大的契约:
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```go
type Reader interface {
Read(p []byte) (n int, err error)
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
type Writer interface {
Write(p []byte) (n int, err error)
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
// Closer 组合了 Reader 和 Writer 的所有方法
type Closer interface {
Reader
Writer
Close() error
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
// 实现 Closer 需要实现 Read/Write/Close 三个方法
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!info] ℹ️ io 包的经典嵌套
> `io.ReadCloser`、`io.WriteCloser`、`io.ReadWriteCloser` 都是 stdlib 中嵌套接口的实际应用。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 常见陷阱总结
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
| 陷阱 | 说明 | 修复 |
|------|------|------|
| nil 接口 vs 含 nil 值的接口 | `(*T)(nil)` 赋值给接口后不为 nil | 注意区分 |
| 类型断言不加 ok | 类型不匹配会 panic | 始终用 `v, ok := x.(T)` |
| 接口不能作为 map key | 接口类型不可比较(除非底层类型可比较) | 用其他字段做 key |
| 空接口失去类型安全 | 取回时需要断言 | Go 1.20+ 用泛型替代部分场景 |