阶段3 泛型与接口 - 代码实例集
阶段 3 泛型与接口 · 代码实例集
覆盖:接口基础 / 接口设计哲学 / 反射 / 泛型 / 接口 vs 泛型抉择。 配合
02-语言特性/04-接口与反射.md、05-泛型.md食用。
1. 接口基础
1.1 隐式实现(无需 implements 关键字)
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string { return "woof" }
type Cat struct{}
func (c Cat) Speak() string { return "meow" }
func announce(s Speaker) { fmt.Println(s.Speak()) }
func main() {
announce(Dog{}) // woof
announce(Cat{}) // meow
}
结构体只要实现了接口所有方法,就自动满足接口,无需声明。
1.2 空接口与 any
var x interface{} = 42
var y any = "hello" // any 是 interface{} 的别名,完全相同
fmt.Println(x, y)
空接口可装任意类型,但取出来要用类型断言。
1.3 类型断言 + 类型 switch
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Printf("unknown: %T\n", v)
}
}
// 安全断言
if s, ok := i.(string); ok {
fmt.Println(s)
}
2. 接口设计哲学
2.1 小接口(Go 的精髓)
// 标准库 io.Reader / io.Writer 就是典范:单一方法,随意组合
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface { Reader; Writer } // 组合
原则:接口越小越易实现、越易复用。别一上来就定义大接口。
2.2 值接收者 vs 指针接收者
type Counter struct{ n int }
func (c Counter) Get() int { return c.n } // 值接收:副本
func (c *Counter) Inc() { c.n++ } // 指针接收:改原值
func main() {
var c Counter
c.Inc() // Go 自动取地址 (&c).Inc()
fmt.Println(c.Get()) // 1
}
- 值接收者:方法内改不动原值,适合只读/小结构体。
- 指针接收者:改原值,或大结构体(避免拷贝)。
- 一致性:一个类型的方法集尽量统一用值或统一用指针。
2.3 接口零值与 nil 陷阱
var s Speaker // s 是 nil 接口
fmt.Println(s == nil) // true
// 但若底层类型指针为 nil,接口非 nil:
var d *Dog
var sp Speaker = d
fmt.Println(sp == nil) // false!接口含 (type=*Dog, value=nil)
// 调用 sp.Speak() 会因方法接收者为 nil 而 panic
3. 反射(reflect)
3.1 TypeOf / ValueOf
import "reflect"
func main() {
x := 42
t := reflect.TypeOf(x) // int
v := reflect.ValueOf(x) // 42
fmt.Println(t, v, v.Int())
}
3.2 修改值(必须传指针)
func main() {
x := 10
v := reflect.ValueOf(&x) // 传指针
v.Elem().SetInt(20) // Elem() 取指向的值,再 Set
fmt.Println(x) // 20
}
// ❌ reflect.ValueOf(x).SetInt(20) 会 panic:x 是值拷贝,不可寻址
3.3 实战:解析结构体 tag(JSON/校验常用)
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age"`
}
func printTags(u interface{}) {
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(f.Name, "→", f.Tag.Get("json"), f.Tag.Get("validate"))
}
}
// 输出:Name → name required / Age → age
反射代价高,仅在序列化、ORM、校验等通用框架层使用,业务代码优先泛型。
4. 泛型(Go 1.18+)
4.1 泛型函数
func Max[T int | float64 | string](a, b T) T {
if a > b { return a }
return b
}
fmt.Println(Max(3, 7), Max(2.1, 1.9), Max("a", "z"))
4.2 内置约束 comparable / any
// comparable:可用 == / != 的类型(map key、去重等)
func Index[T comparable](s []T, target T) int {
for i, v := range s {
if v == target { return i }
}
return -1
}
// any:任意类型
func PrintAll[T any](s []T) {
for _, v := range s { fmt.Println(v) }
}
4.3 自定义约束(接口语法)
type Number interface {
~int | ~int64 | ~float64 // ~ 表示底层类型匹配
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums { total += n }
return total
}
~int 让自定义类型 type MyInt int 也满足约束。
4.4 泛型切片工具(写一次,类型安全复用)
func Map[T, U any](s []T, f func(T) U) []U {
r := make([]U, len(s))
for i, v := range s { r[i] = f(v) }
return r
}
func Filter[T any](s []T, pred func(T) bool) []T {
var r []T
for _, v := range s {
if pred(v) { r = append(r, v) }
}
return r
}
nums := []int{1, 2, 3, 4}
doubled := Map(nums, func(n int) int { return n * 2 }) // [2 4 6 8]
evens := Filter(nums, func(n int) bool { return n%2 == 0 }) // [2 4]
4.5 泛型结构体
type Stack[T any] struct { items []T }
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 { return zero, false }
v := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return v, true
}
4.6 类型推导(大部分情况不用显式写类型参数)
Sum([]int{1, 2, 3}) // 推导出 T=int
Sum([]float64{1.1, 2.2}) // T=float64
5. 接口 vs 泛型:怎么选
| 场景 | 选接口 | 选泛型 |
|---|---|---|
| 方法集多态(不同实现同一行为) | ✅ 运行时多态 | |
| 容器/算法(同逻辑多类型) | ✅ 编译期类型安全、零开销 | |
需要 any 通用存储 | ✅ | |
| 需要数值运算(+, >) | ✅ 约束表达 | |
| 鸭子类型、解耦调用方 | ✅ |
经验:能泛型就泛型(类型安全 + 性能好),需要运行时多态才用接口。
自测题
- Go 的接口实现是隐式还是显式?有什么好处?
- 值接收者和指针接收者的区别?什么时候用指针?
- 反射修改值为什么必须传指针 + Elem()?
- 泛型约束
int | float64与~int | ~float64的区别? - 什么时候该用接口而不是泛型?
- 下面为何 panic?怎么修?
x := 10
reflect.ValueOf(x).SetInt(20)
卡住就问。答案自查
02-语言特性/04-接口与反射.md、05-泛型.md。