Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言泛型

Go语言泛型使用及说明

作者:女王大人万岁

这篇文章主要介绍了Go语言泛型使用及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

一、引言:泛型——Go语言通用化编程的里程碑

Go1.18(2022年3月)前,通用化需求依赖interface{}空接口+类型断言/反射实现,存在代码冗余、类型不安全、运行时易panic等痛点。泛型(参数化类型)通过“类型作为参数”实现“一次定义、多类型复用”,平衡了语法简洁性与通用性,是Go模块化能力的质的飞跃。

本文聚焦泛型核心语法、实战案例、性能特性,对比泛型与反射的优劣,总结最佳使用场景,助力开发者快速掌握并规范落地。

二、Go泛型发展历程(核心里程碑)

Go泛型落地历时8年,贴合极简设计哲学,核心阶段如下:

三、泛型核心语法(必掌握)

泛型本质是“类型参数化”,核心围绕「类型参数列表」「类型约束」「泛型实例化」三大要素,语法统一简洁。

3.1 核心语法要素

3.2 自定义约束示例

// 联合类型约束:限定数值类型
type Numeric interface {
  int | float32 | float64
}
// 行为约束:要求实现String()方法
type Stringer interface {
  String() string
}
// 联合+行为约束
type NumericStringer interface {
  int | float64
  String() string
}

四、泛型全场景实战案例(可直接复用)

4.1 泛型函数(高频场景)

实例1:通用求和(联合类型约束)

package main
import "fmt"
type Numeric interface { int | float32 | float64 }
func Sum[T Numeric](a, b T) T { return a + b }
func main() {
  fmt.Println(Sum(10,20))        // T=int → 30
  fmt.Println(Sum(1.5,2.5))      // T=float64 → 4.0
  fmt.Println(Sum[int](100,200)) // 显式指定 → 300
}

实例2:通用切片去重(comparable约束)

package main
import "fmt"
func RemoveDuplicate[T comparable](slice []T) []T {
  result := make([]T, 0, len(slice))
  tempMap := make(map[T]struct{}, len(slice))
  for _, val := range slice {
    if _, ok := tempMap[val]; !ok {
      tempMap[val] = struct{}{}
      result = append(result, val)
    }
  }
  return result
}
func main() {
  fmt.Println(RemoveDuplicate([]int{1,2,2,3})) // [1 2 3]
  fmt.Println(RemoveDuplicate([]string{"a","b","a"})) // [a b]
}

4.2 泛型结构体与方法(通用数据结构)

实例3:通用栈

package main
import ("errors"; "fmt")
type Stack[T any] struct{ elements []T }
func (s *Stack[T]) Push(e T) { s.elements = append(s.elements, e) }
func (s *Stack[T]) Pop() (T, error) {
  if s.IsEmpty() { return *new(T), errors.New("栈为空") }
  topIdx := len(s.elements)-1
  top := s.elements[topIdx]
  s.elements = s.elements[:topIdx]
  return top, nil
}
func (s *Stack[T]) IsEmpty() bool { return len(s.elements) == 0 }
func main() {
  s := &Stack[int]{}
  s.Push(10); s.Push(20)
  top, _ := s.Pop()
  fmt.Println(top) // 20
}

4.3 泛型接口与嵌套泛型(高级场景)

实例4:通用数据转换器(泛型接口)

package main
import ("fmt"; "strconv"; "strings")
type Converter[T, V any] interface { Convert(T) (V, error) }
// int转string实现
type IntToStringConverter struct{}
func (c *IntToStringConverter) Convert(t int) (string, error) {
  return strconv.Itoa(t), nil
}
// string转User实现
type User struct{ ID int; Name string }
type StringToUserConverter struct{}
func (c *StringToUserConverter) Convert(t string) (User, error) {
  parts := strings.Split(t, ",")
  if len(parts)!=2 { return User{}, fmt.Errorf("格式错误") }
  id, _ := strconv.Atoi(parts[0])
  return User{ID: id, Name: parts[1]}, nil
}
func main() {
  c1 := &IntToStringConverter{}
  fmt.Println(c1.Convert(100)) // 100 <nil>
}

4.4 泛型map(通用键值对容器)

实例5:通用map合并与键值遍历

package main
import "fmt"

// 泛型map合并:将map2合并到map1,key冲突时map2覆盖map1
func MergeMap[K comparable, V any](map1, map2 map[K]V) map[K]V {
    result := make(map[K]V, len(map1)+len(map2))
    // 先复制map1
    for k, v := range map1 {
        result[k] = v
    }
    // 合并map2,覆盖冲突key
    for k, v := range map2 {
        result[k] = v
    }
    return result
}

// 泛型map遍历:提取所有value组成切片
func MapValues[K comparable, V any](m map[K]V) []V {
    values := make([]V, 0, len(m))
    for _, v := range m {
        values = append(values, v)
    }
    return values
}

func main() {
    m1 := map[string]int{"a":1, "b":2}
    m2 := map[string]int{"b":3, "c":4}
    // 合并map
    merged := MergeMap(m1, m2)
    fmt.Println("合并后map:", merged) // map[a:1 b:3 c:4]
    // 提取value切片
    values := MapValues(merged)
    fmt.Println("map所有value:", values) // [1 3 4]
}

4.5 嵌套泛型(泛型类型嵌套使用)

实例6:带数据转换功能的通用栈

package main
import ("errors"; "fmt"; "strconv")

// 复用之前的Converter泛型接口
type Converter[T, V any] interface { Convert(T) (V, error) }

// 嵌套泛型结构体:Stack中存储转换后的V类型数据
type ConvertStack[T, V any] struct {
    stack Stack[V]      // 嵌套泛型结构体Stack
    conv  Converter[T, V] // 嵌套泛型接口Converter
}

// 构造函数
func NewConvertStack[T, V any](conv Converter[T, V]) *ConvertStack[T, V] {
    return &ConvertStack[T, V]{
        stack: Stack[V]{},
        conv:  conv,
    }
}

// 入栈前先转换数据
func (cs *ConvertStack[T, V]) PushAndConvert(t T) error {
    v, err := cs.conv.Convert(t)
    if err != nil {
        return err
    }
    cs.stack.Push(v)
    return nil
}

// 出栈(复用Stack的Pop方法)
func (cs *ConvertStack[T, V]) Pop() (V, error) {
    return cs.stack.Pop()
}

// 复用之前的Stack泛型结构体
type Stack[V any] struct{ elements []V }
func (s *Stack[V]) Push(e V) { s.elements = append(s.elements, e) }
func (s *Stack[V]) Pop() (V, error) {
    if len(s.elements) == 0 { return *new(V), errors.New("栈为空") }
    topIdx := len(s.elements)-1
    top := s.elements[topIdx]
    s.elements = s.elements[:topIdx]
    return top, nil
}

// 实现int转string的Converter
type IntToStringConverter struct{}
func (c *IntToStringConverter) Convert(t int) (string, error) {
    return strconv.Itoa(t), nil
}

func main() {
    cs := NewConvertStack[int, string](&IntToStringConverter{})
    cs.PushAndConvert(10)
    cs.PushAndConvert(20)
    val, _ := cs.Pop()
    fmt.Println("出栈值(已转换):", val) // 20
}

4.6 泛型通道(通用数据传输通道)

实例7:通用通道数据处理

package main
import "fmt"

// 泛型通道处理:从输入通道读取数据,处理后写入输出通道
func ProcessChan[T, V any](inChan <-chan T, outChan chan<- V, process func(T) V) {
    defer close(outChan)
    for t := range inChan {
        v := process(t)
        outChan <- v
    }
}

func main() {
    inChan := make(chan int, 3)
    outChan := make(chan string, 3)

    // 写入数据到输入通道
    inChan <- 10
    inChan <- 20
    inChan <- 30
    close(inChan)

    // 启动处理协程:int转string
    go ProcessChan[int, string](inChan, outChan, func(t int) string {
        return fmt.Sprintf("数值:%d", t)
    })

    // 读取输出通道数据
    for s := range outChan {
        fmt.Println(s) // 依次输出:数值:10、数值:20、数值:30
    }
}

4.7 泛型与函数类型结合(通用函数包装器)

实例8:通用函数耗时统计包装器

package main
import ("fmt"; "time")

// 泛型函数包装器:统计任意函数的执行耗时
func TimeCost[T any, V any](f func(T) V) func(T) V {
    return func(t T) V {
        start := time.Now()
        res := f(t)
        cost := time.Since(start)
        fmt.Printf("函数执行耗时:%v\n", cost)
        return res
    }
}

// 测试函数1:int转string
func IntToStr(t int) string {
    time.Sleep(100 * time.Millisecond)
    return fmt.Sprintf("%d", t)
}

// 测试函数2:计算切片总和
func SliceSum(t []int) int {
    time.Sleep(50 * time.Millisecond)
    sum := 0
    for _, v := range t {
        sum += v
    }
    return sum
}

func main() {
    // 包装并调用IntToStr
    wrappedIntToStr := TimeCost(IntToStr)
    wrappedIntToStr(100) // 输出:函数执行耗时:100+ms

    // 包装并调用SliceSum
    wrappedSliceSum := TimeCost(SliceSum)
    wrappedSliceSum([]int{1,2,3,4,5}) // 输出:函数执行耗时:50+ms
}

核心结论:泛型性能与非泛型代码一致,远优于反射,略优于空接口+类型断言,源于“编译期实例化”特性(无运行时类型开销)。

5.1 三种方案性能对比(实测数据)

测试场景:切片遍历求和;环境:Go1.21,Intel i7-12700H,16GB内存

测试方案每秒操作数平均耗时相对性能
泛型128,571,4297.78 ns/op100%(基准)
空接口+类型断言98,039,21610.20 ns/op76.2%(慢23.8%)
反射12,345,67981.00 ns/op9.6%(慢90.4%)

5.2 性能注意点

六、泛型与反射的优劣对比(选型指南)

对比维度泛型反射
类型安全高(编译期校验,无panic)低(运行时校验,易panic)
性能优秀(与非泛型一致)极差(慢10~100倍)
可读性高(语法简洁,IDE提示友好)低(API复杂,调试难)
通用性中等(编译期已知类型)极高(动态类型,如未知JSON)
适用场景通用工具、数据结构、业务组件序列化、ORM、动态插件

核心选型原则:优先用泛型解决90%通用化需求,仅动态类型场景补充使用反射。

七、泛型核心优势:代码提示与编译期校验

八、最佳使用场景与避坑指南

8.1 最佳场景

8.2 避坑指南

九、总结

Go泛型以“类型参数+类型约束”极简语法,平衡了通用性与简洁性,核心优势是代码复用、类型安全、性能优秀、开发友好。使用原则是“按需使用、简洁设计”,优先解决80%通用化需求,动态场景补充反射。掌握泛型是Go模块化开发的核心能力,助力编写更简洁、安全、高效的代码。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文