引言
Go 從 1.18 開始正式支持泛型,帶來了更強的類型抽象能力,使得我們可以編寫更通用、可復用的代碼。本文檔將介紹下泛型與應用的一些內容
什么是泛型
泛型(Generic)是一種允許你編寫“參數化類型”的編程方式。你可以將類型視為函數的參數,在函數或結構體中使用不同的類型而不重復寫代碼。
這里我們用一個簡單的例子介紹一下基本的應用。
- 求和函數的應用
無泛型寫法
func SumInts(nums []int) int {total := 0for _, v := range nums {total += v}return total
}func SumFloat64s(nums []float64) float64 {total := 0.0for _, v := range nums {total += v}return total
}
泛型寫法
import "golang.org/x/exp/constraints"func Sum[T constraints.Integer | constraints.Float](nums []T) T {var total Tfor _, v := range nums {total += v}return total
}
調用代碼
ints := []int{1, 2, 3}
floats := []float64{1.1, 2.2, 3.3}fmt.Println(Sum(ints)) // 輸出:6
fmt.Println(Sum(floats)) // 輸出:6.6
看上去是不是一下就清爽多了?函數只寫一次,類型可以變化 。這就是簡單的泛型的應用。
那么對泛型你可以理解成一句話:
- 泛型是對類型做"參數化"處理,讓函數或結構體能復用不同的數據類型,而不重復寫代碼。
用人話說就是:
- 我不想因為參數是 int 就寫一遍函數,參數是 float64 又寫一遍,我只想寫一次,能通用就行。
Go 泛型應用
泛型函數
泛型函數允許你對函數的參數和返回值類型進行參數化。
基本語法:
func FuncName[T TypeConstraint](param T) T {// 函數體
}
例如:交換兩個值
func Swap[T any](a, b T) (T, T) {return b, a
}
- T 是類型參數
- any 表示“任意類型”(等價于 interface{})
- 在 Go 1.18 之前(也就是泛型正式加入之前),interface{} 是 Go 中唯一的“通用類型”。它表示一個空接口,可以接受任何類型的值。
- (T, T) 表示返回兩個同類型的值
泛型結構體
你也可以定義"帶類型參數"的結構體或類型:
type Stack[T any] struct {items []T
}func (s *Stack[T]) Push(item T) {s.items = append(s.items, item)
}func (s *Stack[T]) Pop() T {n := len(s.items)item := s.items[n-1]s.items = s.items[:n-1]return item
}
泛型約束
Go 中泛型之所以能“限制”傳入類型,是靠"約束"實現的。
常用約束方式:
- any
代表任何類型(最常用,類似 interface{})
func Print[T any](val T) {fmt.Println(val)
}
- 使用 constraints 包(來自 golang.org/x/exp/constraints)
你需要先引入:
import "golang.org/x/exp/constraints"
約束名 | 類型限制 |
---|---|
constraints.Integer | 所有整數類型(含有符號和無符號) |
constraints.Signed | 只允許有符號整數(int, int64 等) |
constraints.UnSigned | 只允許無符號整數(uint, uint64 等) |
constraints.Float | 只允許浮點數(float32, float64) |
constraints.Ordered | 允許比較大小的類型(數字 + string) |
示例:支持排序的 Min
函數
func Min[T constraints.Ordered](a, b T) T {if a < b {return a}return b
}
注意事項
注意點 | 說明 |
---|---|
不能使用 +、-、<、== 等運算符,除非加了對應約束(如 Ordered) | |
泛型類型不能在運行時反射(不能直接用 reflect.TypeOf[T]) | |
編譯器報錯信息可能較晦澀(需要多實踐) | |
不能對泛型類型的字段做類型斷言(x.(int)) | |
泛型類型定義不能嵌套非確定類型(除非有組合約束) |
實際用例
通用 map 函數
func Map[T any, R any](in []T, f func(T) R) []R {out := make([]R, len(in))for i, v := range in {out[i] = f(v)}return out
}
總結
? 推薦使用泛型的場景:
- 你需要寫工具類、公共庫(如緩存、通用排序等)
- 同樣的邏輯重復出現在多個類型中(int、float、string 等)
- 你想限制傳入類型的范圍,避免濫用 interface{}
? 不推薦使用泛型的場景:
- 項目中類型固定(比如訂單 ID 永遠是
int64
) - 團隊成員不熟悉泛型,增加理解和維護成本
- 為“使用泛型而使用泛型”會讓代碼變復雜