***文末有源码下载链接***
一、为什么需要泛型?
在 Go 1.18 之前,你要么:
-
为每个类型重复写函数(大量重复代码)
-
或用 interface{} + 断言(失去类型安全)
-
或用反射(慢、不安全)
泛型的出现,解决三件事:
-
类型安全,不用写断言
-
减少重复代码
-
比反射快非常多
一句话:Go 泛型是“零成本抽象”的入口。
二、泛型的基本结构
2.1 函数泛型
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
2.2 类型泛型
type Animal[T any] struct {
data []T
}
2.3 核心语法提醒:
-
方括号 [] 放在名字后,不是参数里
-
T 是类型形参,不是值参数
-
可以有多个类型参数
[K V any]
三、类型推断:Go十怎么自动猜T的?
x := Min(1, 3) // T = int
y := Min(1.0, 2.0) // T = float64
z := Min("hello", "Codee君") // T = string
在以下情况自动推断会失效
| 场景 | 会不会推断 |
| 同类型参数 | ✔️ |
| 参数类型不同 | ❌ |
| 没有参数(例如构建泛型类型) | ❌ |
| 返回值没有显示类型 | ❌ |
xx := Min(1,"2") //× 参数类型必须一致
var Cat Animal// ×类型参数不完整
//必须写成
var Cat Animal[string]
四、约束
这是最令人模糊的部分。泛型真正的逻辑在于:约束=类型必须满足什么能力
4.1 any:完全不限制
func Print[T any](x T) {
fmt.Println(x)
}
//等于旧的interface{},但在编译期就是具体类型
4.2 comparable:允许== !=
func IndexOf[T comparable](arr []T, x T) int {
for i, v := range arr {
if v == x {
return i
}
}
return -1
}
arr := []int{1, 2, 3, 4, 5}
fmt.Println(IndexOf(arr, 3))
fmt.Println(IndexOf(arr, 6))
// 输出
//2
//-1
4.3 Ordered:可用 <>的类型
// 来自包"golang.org/x/exp/constraints"
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
4.4 ~Type:匹配任何以Type为底层类型的类型
type MyInt int
// ~int 表示类型约束:T 可以是 int 或任何以 int 为底层类型的自定义类型(如 MyInt)
func Add[T ~int](a, b T) T {
return a + b
}
c := Add[MyInt](1, 2)
fmt.Println(c)
//输出
//3
4.5 接口约束:自定义能力模型
type Number interface {
~int | ~float64
}
func Sum[T Number](arr []T) T {
var sum T
for _, v := range arr {
sum += v
}
return sum
}
fmt.Println(Sum([]float64{1.1, 2.2, 3.3}))
fmt.Println(Sum([]MyInt{1, 2, 3}))
// 输出
//6.6
//6
五、any vs T:本质区别是什么?
any不等于泛型。any是一种约束,意思是:该类型参数没有任何限制。但T仍然是“泛型参数”,不是空接口
| 写法 | 意义 |
| T any | 使用泛型,但无约束 |
| interface{} | 完全没有泛型行为,需要断言 |
| any | interface{}的别名 |
func Get(x any) // x 是 interface{}类型
func Get[T any](x T) // x 是具体类型
后者是泛型,前者不是
六、泛型类型、泛型结构体、泛型方法
6.1 结构体泛型
type Animal[T any] struct {
data []T
}
6.2 方法接收者泛型
func (a *Animal[T]) Append(b T) {
a.data = append(a.data, b)
}
七、泛型的性能:与接口和反射对比
7.1 泛型vs接口
| 项目 | 泛型 | 接口 |
| 类型安全 | ✔️ | ❌需要断言 |
| 运行时成本 | ✔️无 | ❌调度开销 |
| 使用体验 | 稍复杂 | 简单 |
| 编译结果 | 每种类型实例化一个版本 | 单一版本 |
泛型性能更好,尤其在高频调用的热路径。
7.2 泛型vs反射
泛型完全碾压反射:
-
无需运行时类型判断
-
无需unsafe.Pointer
-
无需大量内存分配
-
优化空间更大
八、泛型常见错误
8.1 ×给泛型方法定义自己的类型参数
a
[T]
[U any]
x
8.2 ×误用any
any不是万能钥匙,你可能反而失去约束能力
8.3 ×想用泛型实现“运行时类型变化”
泛型是在编译器处理的,不能动态变类型。
九、工程实践:什么时候应该用泛型?
9.1 应该用:
-
数据结构:栈、队列、集合、树
-
集合操作:Map、Filter、Reduce
-
数字运算库
-
数据仓库模板
-
业务逻辑确实可复用并且类型不同
9.2 ×不应该用:
-
业务场景逻辑简单,不需要复杂抽象
-
过度泛型导致代码难懂
-
运行时类型不同(适合用接口+多态)
泛型用于抽象“跨类型但逻辑一致”的代码,不适合抽象“行为不同但名字相似”的场景。
十、实战:通用Repository实现
// 数据资源接口
type IRepo[T IModel] interface {
// 分页查询
PageList(c *gin.Context, query *IFilter) (res *response.PageListT[T], err error)
// 分页查询
PageListWithSelectOption(c *gin.Context, query *IFilter, selectOpt []string) (res *response.PageListT[T], err error)
// 查询一个
One(c *gin.Context, id uint) (res T, err error)
// 查询一个
OneWithSelectOption(c *gin.Context, id uint, selectOpt []string) (res T, err error)
// 根据名称查询
OneByName(c *gin.Context, name string) (res T, err error)
// 根据名称查询
OneByNameWithSelectOption(c *gin.Context, name string, selectOpt []string) (res T, err error)
// 添加
Add(c *gin.Context, model T) (newId uint, err error)
// 更新,传什么就更新什么
Update(c *gin.Context, updateFields map[string]any, id uint) (updated bool, err error)
// 删除
Delete(c *gin.Context, id uint) (deleted bool, err error)
}
// 数据资源接口实现
type Repo[T IModel] struct {
DB *gorm.DB
}
// 新建一个数据资源
func NewRepo[T IModel](db *gorm.DB) *Repo[T] {
return &Repo[T]{
DB: db,
}
}
// 分页查询数据
func (r *Repo[T]) PageList(c *gin.Context, f *IFilter) (res *response.PageListT[T], err error) {
db := r.DB
db = (*f).BuildPageListFilter(c, db)
offset := ((*f).GetPage() - 1) * (*f).GetPageSize()
db = db.Model(new(T)).Offset(int(offset)).Limit(int((*f).GetPageSize()))
objs := make([]T, 0)
err = db.Find(&objs).Error
var count int64
db.Offset(-1).Limit(-1).Select("count(id)").Count(&count)
res = &response.PageListT[T]{
List: objs,
Pages: response.MakePages(count, (*f).GetPage(), (*f).GetPageSize()),
}
return
}
*源码地址*
1、公众号“Codee君”回复“每日一Go”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!