Go 泛型的约束与类型集合怎么理解?和接口怎么取舍?
一句话结论:泛型约束本质是“类型集合”:可以是接口(方法集),也可以是类型集(~ 底层类型、| 联合类型),函数或类型带类型参数后由编译器在调用处实例化;泛型是编译期的“类型参数化”,接口是运行期的“行为抽象”——同构算法选泛型,需要多实现多态选接口。
类型集合怎么读
- any = interface{}:所有类型组成的集合。
- comparable:可用 == / != 比较的类型集合,内建约束。
- interface{ ~int | ~string }:底层类型是 int 或 string 的类型。
- 自定义约束:type Number interface { ~int |
float64 },在约束中“|”表示并集、“”表示含底层类型。
泛型 vs 接口
| 维度 | 泛型 | 接口 |
|---|---|---|
| 生效时机 | 编译期实例化(单态化) | 运行期动态分派 |
| 类型安全 | 强,调用时即确定 | 可能需断言 |
| 性能 | 无装箱,通常更快 | 有动态分派与潜在装箱 |
| 灵活性 | 类型需编译期确定 | 实现可在运行期替换 |
| 适用场景 | 容器、算法、统一处理类型结构 | 多实现、插件、依赖注入、测试替身 |
口诀:能用接口表达清楚“行为”就先上接口;只有“同一套算法要套用到多种具体类型”时才值得用泛型。泛型还会让编译更慢、错误信息更绕。
代码示例
// 类型集合约束:可比较大小的一类数值
type Number interface {
~int | ~int64 | ~float64
}
func Min[T Number](a, b T) T {
if a < b {
return a
}
return b
}
fmt.Println(Min[int](3, 5))
fmt.Println(Min(1.5, 2.5)) // 类型推断
常见追问 / 记忆点
- 追问:~int 和 int 作约束有何区别?——~int 允许底层类型是 int 的自定义类型,int 只允许字面 int 本身。
- 追问:为什么接口也能当约束?——约束就是类型集合,方法集与类型集都是集合,语法上统一用接口表达。
- 记忆点:泛型 = 编译期模板化,零运行时开销但增加编译负担;接口 = 运行期多态。先用接口,必要时泛型。