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
}
任何同时实现了 Read 和 Write 方法的类型,就自动实现了 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
结构体零值不省 → 用指针