golang 接口的实现
·
一、先理解什么是接口
接口就是定义一组行为(方法),但不关心具体怎么实现。就像“交通工具”这个接口,定义了“能移动”、“能刹车”,至于汽车、自行车怎么实现这些功能,接口不管。
二、Go 接口的特点(跟其他语言不一样)
最关键:Go 的接口是隐式实现的
-
Java、C# 需要显式写
implements -
Go 不需要!只要一个类型拥有接口要求的所有方法,它就自动实现了该接口
三、底层结构(简单理解)
Go 接口在内存中占 2 个字(16 字节,64 位系统):
-
第一个字:指向类型信息(这个变量是什么类型)
-
第二个字:指向数据本身(或者数据指针)
接口变量 = [类型指针] [数据指针]
四、基础例子
package main
import "fmt"
// 定义一个接口
type Speaker interface {
Speak() string
}
// 定义 Dog 结构体
type Dog struct {
Name string
}
// Dog 实现 Speak 方法(自动就实现了 Speaker 接口)
func (d Dog) Speak() string {
return "汪汪!我是" + d.Name
}
// 定义 Cat 结构体
type Cat struct {
Name string
}
// Cat 实现 Speak 方法
func (c Cat) Speak() string {
return "喵喵!我是" + c.Name
}
func main() {
var speaker Speaker // 声明接口变量
speaker = Dog{Name: "旺财"}
fmt.Println(speaker.Speak()) // 汪汪!我是旺财
speaker = Cat{Name: "咪咪"}
fmt.Println(speaker.Speak()) // 喵喵!我是咪咪
}
发生了什么?
-
speaker = Dog{...}时,Go 自动把 Dog 类型信息 + Dog 数据 存入接口 -
调用
speaker.Speak()时,Go 通过类型指针找到 Dog 的Speak方法执行
五、更形象的例子(空的接口)
// interface{} 可以表示任何类型(所有类型都实现了空接口)
var anything interface{}
anything = 42
fmt.Println(anything) // 42
anything = "hello"
fmt.Println(anything) // hello
anything = Dog{Name: "小黑"}
fmt.Println(anything.(Dog).Name) // 小黑(需要类型断言)
六、类型断言(把接口里的数据取出来)
var i interface{} = "hello"
// 方式1:带检查的安全断言
s, ok := i.(string)
if ok {
fmt.Println("是字符串:", s)
}
// 方式2:不检查(万一失败会 panic)
// s2 := i.(string)
// 方式3:type switch
switch v := i.(type) {
case string:
fmt.Println("字符串:", v)
case int:
fmt.Println("整数:", v)
default:
fmt.Println("其他类型")
}
七、指针接收者 vs 值接收者(重要!)
type Mover interface {
Move()
}
type Car struct{}
// 值接收者
func (c Car) Move() {
fmt.Println("汽车在移动")
}
type Bike struct{}
// 指针接收者
func (b *Bike) Move() {
fmt.Println("自行车在移动")
}
func main() {
var m Mover
m = Car{} // ✅ 值可以
m = &Car{} // ✅ 指针也可以(Go 自动解引用)
m = Bike{} // ❌ 编译错误!Bike 没有实现 Move(因为方法需要指针)
m = &Bike{} // ✅ 指针可以
// 规则:如果接口方法是值接收者,值和指针都能赋值
// 如果接口方法是指针接收者,只有指针能赋值
}
八、nil 接口 vs 非 nil 接口
var s1 Speaker // nil 接口:类型=nil, 数据=nil
fmt.Println(s1 == nil) // true
var d *Dog = nil
var s2 Speaker = d // 不是 nil 接口!类型=*Dog, 数据=nil
fmt.Println(s2 == nil) // false
// 调用会怎样?
// s2.Speak() // panic!方法内如果访问 d.Name 会空指针
九、底层实现图示
正常接口(非空):
┌──────────────┐
│ 类型指针 │ -> 指向 Dog 的元数据(包含方法表)
├──────────────┤
│ 数据指针 │ -> 指向 Dog 实例 {"旺财"}
└──────────────┘
空接口 interface{}:
┌──────────────┐
│ 类型指针 │ -> 指向数据类型
├──────────────┤
│ 数据指针 │ -> 指向数据
└──────────────┘
十、实际应用场景
// 场景1:标准库的 Stringer 接口
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d岁)", p.Name, p.Age)
}
// 这样 fmt.Println(person) 就会自动调用 String()
// 场景2:排序接口
type Students []Student
func (s Students) Len() int { return len(s) }
func (s Students) Less(i, j int) bool { return s[i].Score > s[j].Score }
func (s Students) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// sort.Sort(students) 就能排序
// 场景3:错误处理
type MyError struct {
Msg string
}
func (e *MyError) Error() string {
return e.Msg
}
总结(记住这 4 点)
-
隐式实现:不需要写
implements,有方法就自动实现 -
鸭子类型:看起来像鸭子,叫起来像鸭子,就是鸭子
-
双字结构:类型指针(找方法)+ 数据指针(找数据)
-
灵活性:解耦调用方和实现方,依赖接口而非具体类型
记忆口诀:
接口定行为,类型去实现
不用写继承,有方法就算
底层俩指针,类型和数据
判空要注意,类型非 nil 也不行
更多推荐


所有评论(0)