***文末有源码下载链接***
一、为什么需要泛型高阶实战?
写泛型不难,难的是写”好“泛型。
你会遇到如下情况:
-
如何设计一个真正可复用的约束?
-
一个泛型容器应该如何实现并发安全、零拷贝、友好API?
-
如何抽象出可复用算法而不是写一堆if/else?
-
如何让泛型代码不慢?
今天的内容旧让你做到:能写工具库、能写自己的容器、能写可复用的算法。
二、构建泛型工具库
工具库是泛型最适合发挥的地方:类型无关、逻辑可复用
2.1 Maybe[T]:一个常用的函数式泛型工具
类似Option类型,避免nil
type Maybe[T any] struct {
v T
ok bool
}
func Some[T any](v T) Maybe[T] { return Maybe[T]{v, true} }
func None[T any]() Maybe[T] { return Maybe[T]{ok: false} }
func (m Maybe[T]) Get() (T, bool) { return m.v, m.ok }
func (m Maybe[T]) Or(defaultV T) T {
if m.ok {
return m.v
}
return defaultV
}
// 查找map中是否存在key
func lookupMaybe[T comparable, U any](m map[T]U, k T) Maybe[U] {
if v, ok := m[k]; ok {
return Some(v)
}
return None[U]()
}
users := map[int]string{
100: "Codee君",
200: "詹姆斯邦德",
300: "乔治华盛顿",
}
res1 := lookupMaybe(users, 100)
if name, ok := res1.Get(); ok {
fmt.Println(name)
}
res2 := lookupMaybe(users, 011)
nameOrDefault := res2.Or("未知")
fmt.Println(nameOrDefault)
nameOrDefault2 := res1.Or("未知")
fmt.Println(nameOrDefault2)
// 输出
Codee君
未知
Codee君
Maybe[T] 类型的主要优势在于它使代码流更清晰,特别是通过 Or() 这样的方法,将 缺失值处理逻辑(提供默认值)与操作逻辑(查找) 优雅地结合起来,避免了每次查找后都写 if ok { ... } else { ... } 这样的样板代码。
2.2 转换函数与过滤器
package main
// 切片A转切片B
func SliceA2B[A any, B any](a []A, f func(A) B) []B {
b := make([]B, len(a))
for i, v := range a {
b[i] = f(v)
}
return b
}
// 切片过滤
func Filter[T any](in []T, f func(T) bool) []T {
out := make([]T, 0)
for _, v := range in {
if f(v) {
out = append(out, v)
}
}
return out
}
func main() {
fmt.Println("Hello, Codee君!")
numbers := []int{1, 2, 3, 4, 5}
// 转换成字符串
strings := SliceA2B(numbers, func(i int) string {
return fmt.Sprintf("ID=%d", i)
})
fmt.Println(strings)
// 过滤出偶数
even := Filter(numbers, func(i int) bool {
return i%2 == 0
})
fmt.Println(even)
}
//输出
Hello, Codee君!
[ID=1 ID=2 ID=3 ID=4 ID=5]
[2 4]
三、泛型容器
容器是泛型最能发挥威力的地方。
3.1 泛型栈:并发安全,零拷贝,API友好
type Stack[T any] struct {
data []*T
mu sync.Mutex
len atomic.Int64
}
func NewStack[T any]() *Stack[T] {
return &Stack[T]{
data: make([]*T, 0),
}
}
func (s *Stack[T]) Push(v *T) {
s.mu.Lock()
defer s.mu.Unlock()
// 避免元素复制:直接存储指针*T
s.data = append(s.data, v)
s.len.Add(1)
}
func (s *Stack[T]) Pop() *T {
s.mu.Lock()
defer s.mu.Unlock()
if s.len.Load() == 0 {
return nil
}
v := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
s.len.Add(-1)
return v
}
// 辅助API
// 返回栈当前元素数量
func (s *Stack[T]) Len() int64 {
return s.len.Load()
}
// 栈
stack := NewStack[string]()
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
value := fmt.Sprintf("value-%d", i)
stack.Push(&value)
}(i)
}
wg.Wait()
fmt.Println("栈元素数量:", stack.Len())
if itm, ok := stack.Pop(); ok {
fmt.Println(*itm)
fmt.Println("栈元素数量:", stack.Len())
}
// 输出
栈元素数量: 100
value-99
栈元素数量: 99
| 目标 | 达成情况 |
| 并发安全 | 使用sync.Mutex |
| 零拷贝 | 通过存储指针(*T),避免了在Push/Pop时的元素值复制 |
| 友好API | 提供了Push,Pop,Len等简洁方法 |
3.2 并发安全的泛型Map
type CodeeMap[K comparable, V any] struct {
data map[K]V
mu sync.RWMutex
}
func NewCodeeMap[K comparable, V any]() *CodeeMap[K, V] {
return &CodeeMap[K, V]{
data: make(map[K]V),
}
}
func (m *CodeeMap[K, V]) Set(k K, v V) {
m.mu.Lock()
defer m.mu.Unlock()
m.data[k] = v
}
func (m *CodeeMap[K, V]) Get(k K) (V, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
v, ok := m.data[k]
return v, ok
}
// 映射
m := NewCodeeMap[int, string]()
m.Set(1, "ID=1")
m.Set(2, "ID=2")
m.Set(3, "ID=3")
fmt.Println(m.Get(1))
fmt.Println(m.Get(2))
fmt.Println(m.Get(3))
fmt.Println(m.Get(4))
// 输出
ID=1 true
ID=2 true
ID=3 true
false
四、泛型算法
4.1 定制可排序泛型函数
// 需要导入内置包golang.org/x/exp/constraints
// 定制可排序约束
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
// Ordered的意义:支持数字、string、rune等,只要是可用比大小的类型都可以
4.2 泛型二分查找
// 泛型二分查找
func BinarySearch[T constraints.Ordered](arr []T, target T) int {
left, right := 0, len(arr)-1
for left <= right {
mid := left + (right-left)/2
if arr[mid] == target {
return mid
} else if arr[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
4.3 泛型归约运算
// 泛型归约算法
func Reduce[T any, R any](arr []T, init R, f func(R, T) R) R {
acc := init
for _, v := range arr {
acc = f(acc, v)
}
return acc
}
sum := Reduce([]int{1, 2, 3, 4}, 0, func(a, b int) int { return a + b })
fmt.Println(sum)
mult := Reduce([]int{1, 2, 3, 4}, 1, func(a, b int) int { return a * b })
fmt.Println(mult)
//输出
10
24
4.4 泛型优先队列
// 泛型优先队列
type Item[T any] struct {
value T
Priority int
}
type PriorityQueue[T any] []*Item[T]
func (pq PriorityQueue[T]) Len() int {
return len(pq)
}
func (pq PriorityQueue[T]) Less(i, j int) bool {
return pq[i].Priority < pq[j].Priority
}
func (pq PriorityQueue[T]) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueue[T]) Push(x interface{}) {
item := x.(*Item[T])
*pq = append(*pq, item)
}
func (pq *PriorityQueue[T]) Pop() interface{} {
old := *pq
n := len(old)
if n == 0 {
var zero T
return zero
}
item := old[n-1]
*pq = old[0 : n-1]
return item
}
func NewPriorityQueue[T any]() *PriorityQueue[T] {
return &PriorityQueue[T]{}
}
type Task struct {
Name string
DueDate time.Time
}
import "container/heap"
// 优先队列
pq := make(PriorityQueue[Task], 0)
tasks := []Task{
{
Name: "任务1",
DueDate: time.Now(),
},
{
Name: "任务2",
DueDate: time.Now().AddDate(0, 0, 2),
},
{
Name: "任务3",
DueDate: time.Now().AddDate(0, 0, 1),
},
}
for _, task := range tasks {
// 将Task包装在Item中,并根据DueDate设置优先级(时间越早优先级越高)
priority := int(time.Since(task.DueDate).Milliseconds())
if priority < 0 {
priority = -priority // 确保过期时间越早,优先级越高
}
heap.Push(&pq, &Item[Task]{
value: task,
Priority: priority,
})
}
for pq.Len() > 0 {
item := heap.Pop(&pq).(*Item[Task])
fmt.Println(item.value)
}
//输出
{任务1 2025-12-02 11:01:56.3726019 +0800 CST m=+0.001558601}
{任务3 2025-12-03 11:01:56.4072894 +0800 CST}
{任务2 2025-12-04 11:01:56.3726019 +0800 CST}
五、性能:泛型是不是会变慢?
泛型不会让性能变差,通常比空接口更快。原因是:
-
泛型是编译期静态展开的
-
没有运行时类型断言
-
没有接口装箱
-
编译器能内联
在容器、工具库中几乎都是净性能收益。
六、总结
以上这些模式广泛应用于:微服务框架、数据库驱动、ORM、业务工具库、缓存系统、并发数据结构,掌握这些方法,对你今后的工作和学习有巨大帮助!
*源码地址*
1、公众号“Codee君”回复“每日一Go”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!