十分鐘了解 Golang 泛型
可能有人會覺得Go泛型很難,因此想要借鑒其他語言(比如Java、NodeJS)的泛型實踐。事實上Go泛型很容易學(xué),本文希望能幫助讀者更好的理解Go泛型。
注:本文不會將 Go 泛型與其他語言的泛型實現(xiàn)進(jìn)行比較,但會幫助你理解 Go 泛型元素背后的上下文、結(jié)構(gòu)及其原理。
一、前置條件
要編寫本文中的示例代碼,需要:
- 在計算機上安裝 Go 1.18+
- 對Golang結(jié)構(gòu)、類型、函數(shù)和方法有最低限度的了解
二、概述
在 2020 年之前,Go泛型既是風(fēng)險也是機遇。
當(dāng) Go 泛型在 2009 年左右被首次提出時(當(dāng)時該編程語言已經(jīng)公開),該特性是 Go 語言的主要弱點之一(Go 團(tuán)隊調(diào)查發(fā)現(xiàn))。
此后,Go 團(tuán)隊在 Go 草案設(shè)計中接受了許多泛型實現(xiàn),并在 Go 1.18 版本[2]中首次引入了泛型。
Go 博客 2020 調(diào)查結(jié)果
Go 2020 調(diào)查顯示,自 Go 語言誕生以來,Go 社區(qū)一直要求引入泛型功能。
Go 開發(fā)人員(以及 Go 團(tuán)隊成員)看到這一缺陷阻礙了 Go 語言的發(fā)展,同時,如果得到修復(fù),Go將具有更大的靈活性和性能。
1.什么是程序設(shè)計中的泛型?
根據(jù)維基百科[3]的解釋,泛型編程是一種計算機編程風(fēng)格,在這種編程風(fēng)格中,算法的具體類型可以在以后指定。
簡單解釋一下:泛型是一種可以與多種類型結(jié)合使用的類型,泛型函數(shù)是一種可以與多種類型結(jié)合使用的函數(shù)。
?? 簡單提一下:盡管"泛型"在過去和現(xiàn)在都可以通過 interface{}、反射包或代碼生成器在 Go 中實現(xiàn),但還是要提一下在使用這三種方法之前需要仔細(xì)考慮。
為了幫助我們以實用的方式理解和學(xué)習(xí) Go 泛型,我們將在本文稍后部分提供示例代碼。
但要知道,既然 Go 泛型已經(jīng)可用,就可以消除模板代碼,不必?fù)?dān)心向后兼容問題,同時還能編寫可重用、類型安全和可維護(hù)的代碼。
2.那么......為什么需要 Go 泛型?
簡而言之,最多可提高 20% 性能。
根據(jù) Go 博客的描述,Go 泛型為 Go 語言增加了三個主要組件:
- 函數(shù)和類型的類型參數(shù)。
- 將接口類型定義為類型集,包括沒有方法的類型。
- 類型推導(dǎo),允許在調(diào)用函數(shù)時省略類型參數(shù)。
3.在 Go 1.18 之前沒有這種功能嗎?
從技術(shù)上講,早在 Go 泛型發(fā)布之前,Go 就有一些處理"泛型"的方法:
- 使用"泛型"代碼生成器生成 Go 軟件包,如 https://github.com/cheekybits/genny[4]
- 使用帶有switch語句和類型轉(zhuǎn)換的接口
- 使用帶有參數(shù)驗證的反射軟件包
然而,與正式的Go泛型相比,這些方法還遠(yuǎn)遠(yuǎn)不夠,有如下缺點:
- 使用類型switch和轉(zhuǎn)換時性能較低
- 類型安全損耗:接口和反射不是類型安全的,這意味著代碼可能會傳遞任何類型,而這些類型在編譯過程中會被忽略,從而在運行時引起panic。
- Go 項目構(gòu)建更復(fù)雜,編譯時間更長
- 可能需要對調(diào)用代碼和函數(shù)代碼進(jìn)行類型斷言
- 缺乏對自定義派生類型的支持
- 代碼可讀性差(使用反射時更明顯)
??注:上述觀點并不意味著在 Go 編程中使用接口或反射包不好;它們還有其他用途,應(yīng)該在合適的場景下應(yīng)用。
巧合的是,上述幾點 ?? 使 Go 泛型適合處理目前在 Go 中的泛型實現(xiàn),因為:
- 類型安全 (運行時不會丟失類型,也不需要類型驗證、切換或轉(zhuǎn)換)
- 高性能
- Go IDE 的支持
- 向后兼容 (使用 Go 1.18+ 重構(gòu)后,舊版代碼仍可運行)
- 對自定義數(shù)據(jù)類型的高度支持
三、入門:使用 Go 泛型
在開始重構(gòu)之前,我們借助一個迷你 Go 程序來了解 Go 泛型使用的一些術(shù)語和邏輯。
作為實操案例,我們將首先在不使用 Go 泛型的情況下解決 Leetcode 問題。然后,隨著我們對這一主題的了解加深,我們將使用 Go 泛型對其進(jìn)行重構(gòu)。
Leetcode 問題
有幾家公司在技術(shù)面試時都問過這個問題,我們對措辭稍作改動,但邏輯不變。Leetcode 鏈接為:https://leetcode.com/problems/contains-duplicate[5]。
??問題:給定一個整型(int 或 in32 或 int64)數(shù)組 nums,如果任何值在數(shù)組中至少出現(xiàn)兩次,則返回 true;如果每個元素都不同,則返回 false。
現(xiàn)在,我們在不使用 Go 泛型的情況下解決這個問題。
進(jìn)入開發(fā)目錄,創(chuàng)建一個新的 Go 項目目錄,名稱不限。我將其命名為 leetcode1。然后將目錄更改為新創(chuàng)建的項目目錄。
按照慣例,我們在終端的項目根目錄下運行 go mod init github.com/username/leetcode1,為項目創(chuàng)建一個 Go 模塊。
記住:不要忘記將username替換為你自己的 Github 用戶名。
接下來,創(chuàng)建 leetcode.go 文件并將下面的代碼復(fù)制進(jìn)去:
package main
import "fmt"
type FilterInt map[int]bool
type FilterInt32 map[int32]bool
type FilterInt64 map[int64]bool
func main() {
data := []int{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data32 := []int32{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data64 := []int64{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
fmt.Printf("Duplicate found %t\n", FindDuplicateInt(data))
fmt.Printf("Duplicate found %t\n", FindDuplicateInt32(data32))
fmt.Printf("Duplicate found %t\n", FindDuplicateInt64(data64))
}
func FindDuplicateInt(data []int) bool {
inArray := FilterInt{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
func FindDuplicateInt32(data []int32) bool {
inArray := FilterInt32{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
func FindDuplicateInt64(data []int64) bool {
inArray := FilterInt64{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
func (r FilterInt) add(datum int) {
r[datum] = true
}
func (r FilterInt32) add(datum int32) {
r[datum] = true
}
func (r FilterInt64) add(datum int64) {
r[datum] = true
}
func (r FilterInt) has(datum int) bool {
_, ok := r[datum]
return ok
}
func (r FilterInt32) has(datum int32) bool {
_, ok := r[datum]
return ok
}
func (r FilterInt64) has(datum int64) bool {
_, ok := r[datum]
return ok
}
再看一下 Leetcode 的問題,程序應(yīng)該檢查輸入的數(shù)組(可以是 INT、INT32 或 INT64),并找出是否有重復(fù)數(shù)據(jù),如果有則返回 true,否則返回 false,上面這段代碼就是完成這個任務(wù)的。
在第 10、11 和 12 行,分別提供了 int、int32 和 int64 類型數(shù)據(jù)的示例數(shù)組。
在第 5、6 和 7 行,分別創(chuàng)建了關(guān)鍵字類型為 int、int32 和 int64 的 map 類型 FilterInt、FilterInt32 和 FilterInt64。
所有類型 map 的值都是布爾值,所有類型都有相同的 has 和 add 方法。從本質(zhì)上講,add 方法將接受 datum 參數(shù),并在 map 中創(chuàng)建值為 true 的鍵。根據(jù) map 是否包含作為 datum 傳入的鍵,has 方法將返回 true 或 false。
現(xiàn)在,第 18 行的函數(shù) FindDuplicateInt、第 29 行的函數(shù) FindDuplicateInt32 和第 40 行的函數(shù) FindDuplicateInt64 實現(xiàn)了相同的邏輯,即驗證所提供的數(shù)據(jù)中是否存在重復(fù)數(shù)據(jù),如果發(fā)現(xiàn)重復(fù)數(shù)據(jù),則返回 true,否則返回 false。
看看這些重復(fù)代碼。
有沒有讓你感到惡心???
總之,如果我們在終端運行項目根目錄下的 go run leetcode.go,就會編譯成功并運行。輸出結(jié)果應(yīng)該與此類似:
Duplicate found true
Duplicate found true
Duplicate found true
如果我們要查找 float32、float64 或字符串的重復(fù)內(nèi)容,該怎么辦?
我們可以為每種類型編寫一個實現(xiàn),為不同類型明確編寫多個函數(shù),或者使用接口,或者通過包生成"泛型"代碼。這就是"泛型"誕生的過程。
通過泛型,我們可以編寫泛型函數(shù)來替代多個函數(shù),或使用帶有類型轉(zhuǎn)換的接口。
接下來我們用泛型來重構(gòu)代碼,但首先需要熟悉一些術(shù)語和概念。
四、泛型基礎(chǔ)知識
1.類型參數(shù)
類型參數(shù)的可視化表示
上圖描述的是泛型函數(shù) FindDuplicate,T 是類型參數(shù),any 是類型參數(shù)的約束條件(接下來將討論約束條件)。
類型參數(shù)就像一個抽象的數(shù)據(jù)層,通常用緊跟函數(shù)或類型名稱的方括號中的大寫字母(多為字母 T)來表示。下面是一些例子:
...
// map type with type parameter T and constraint comparable
type Filter[T comparable] map[T]bool
...
...
// Function FindDuplicate with type parameter T and constraint any
func FindDuplicate[T any](data T "T any") bool {
// find duplicate code
}
...
2.類型推導(dǎo)
泛型函數(shù)必須了解其支持的數(shù)據(jù)類型,才能正常運行。
??要點:泛型類型參數(shù)的約束條件是在編譯時由調(diào)用代碼確定的代表單一類型的一組類型。
進(jìn)一步來說,類型參數(shù)的約束代表了一系列可允許的類型,但在編譯時,類型參數(shù)只代表一種類型,因為 Go 是一種強類型的靜態(tài)檢查語言。
??提醒:由于 Go 是一種強類型的靜態(tài)語言,因此會在應(yīng)用程序編譯期間而非運行時檢查類型。Go 泛型解決了這個問題。
類型由調(diào)用代碼類型推導(dǎo)提供,如果泛型類型參數(shù)的約束條件不允許使用該類型,代碼將無法編譯。
符合參數(shù)約束的類型
由于類型是通過約束知道的,因此在大多數(shù)情況下,編譯器可以在編譯時推斷出參數(shù)類型。
通過類型推導(dǎo),可以避免從調(diào)用代碼中為泛型函數(shù)或泛型類型實例化進(jìn)行人工類型推導(dǎo)。
??注意:如果編譯器無法推斷類型(即類型推導(dǎo)失?。?,可以在實例化時或在調(diào)用代碼中手動指定類型。
下面是 FindDuplicate 泛型函數(shù)的一個很好的示例:
FindDuplicate 泛型函數(shù)示例
我們可以忽略調(diào)用代碼中的 [int],因為編譯器會推斷出[int],但我更傾向于加入[int]以提高代碼的可讀性。
3.約束
在引入泛型之前,Go 接口用于定義方法集。然而,隨著泛型約束的引入,接口現(xiàn)在既可以定義類型集,也可以定義方法集。
約束是用于指定允許使用的泛型的接口,在上述 FindDuplicate 函數(shù)中使用了 any 約束。
??Pro 提示:除非必要,否則避免使用 any 接口約束。
在底層實現(xiàn)上,any關(guān)鍵字只是一個空接口,這意味著可以用 interface{} 替換,編譯時不會出現(xiàn)任何錯誤。
Go 泛型中約束的可視化表示
上述接口約束允許使用 int、int16、int32 和 int64 類型。這些類型是約束聯(lián)合體,用管道符 | 分隔類型。
約束在以下幾個方面有好處:
- 通過類型參數(shù)定義了一組允許的類型
- 明確發(fā)現(xiàn)泛型函數(shù)的誤用
- 提高代碼可讀性
- 有助于編寫更具可維護(hù)性、可重用性和可測試性的代碼
?? 簡單提一下:使用約束時有一個小問題
請看下面的代碼:
package main
import "fmt"
type CustomType int16
func main() {
var value CustomType
value = 2
printValue(value)
}
func printValue[T int16](value T "T int16") {
fmt.Printf("Value %d", value)
}
在上面的代碼中,第 5 行定義了一個名為 CustomType 的自定義類型,其基礎(chǔ)類型為 int16。
在第 8 行,聲明了一個以 CustomType 為類型的變量,并在第 9 行為其賦值。
然后,在第 10 行調(diào)用帶有值的 printValue 泛型函數(shù)。
...??
...??
你認(rèn)為代碼可以編譯運行嗎?
如果我們在終端執(zhí)行 go run custom-generics.go,就會出現(xiàn)這樣的錯誤。
./custom-type-generics.go:10:12: CustomType does not implement int16 (possibly missing ~ for int16 in constraint int16)
盡管自定義類型 CustomType 是 int16 類型,但 printValue 泛型函數(shù)的類型參數(shù)約束無法識別。
鑒于函數(shù)約束不允許使用該類型,這也是合理的。不過,可以修改 printValue 函數(shù),使其接受我們的自定義類型。
現(xiàn)在,更新 printValue 函數(shù)如下:
func printValue[T int16 | CustomType](value T "T int16 | CustomType") {
fmt.Println(value)
}
使用管道操作符,我們將自定義類型 CustomType 添加到 printValue 泛型函數(shù)類型參數(shù)的約束中,現(xiàn)在有了一個聯(lián)合約束。
如果我們再次運行該程序,編譯和運行都不會出現(xiàn)任何錯誤。
但是,等等!為什么需要 int16 類型和"int16"類型的約束聯(lián)合?
我們將在下一節(jié)介紹波浪線 ~ 運算符。
4.波浪線(Tilde)運算符和基礎(chǔ)類型
幸運的是,Go 1.18 通過波浪線運算符引入了底層類型,波浪線運算符允許約束支持底層類型。
在上一步代碼示例中,CustomType 類型的底層類型是 int16?,F(xiàn)在,我們使用 ~ 波浪線更新 printValue 泛型函數(shù)類型參數(shù)的約束,如下所示:
func printValue[T ~int16](value T "T ~int16") {
fmt.Println(value)
}
新代碼應(yīng)該是這樣的:
package main
import "fmt"
type CustomType int16
func main() {
var value CustomType
value = 2
printValue(value)
}
func printValue[T ~int16](value T "T ~int16") {
fmt.Printf("Value %d", value)
}
再次運行程序,應(yīng)該可以成功編譯和運行。我們刪除了約束聯(lián)合,并在約束中的 int16 類型前用 ~ 波浪線運算符替換了 CustomType。
編譯器現(xiàn)在可以理解,CustomType 類型之所以可以使用,僅僅是因為它的底層類型是 int16。
?? 簡單來說,~ 告訴約束接受任何 int16 類型以及任何以 int16 作為底層類型的類型。
下面是一個泛型約束接口示例,它也允許函數(shù)聲明:
type Number interface {
int | float32 | float64
IsEven() bool
}
不過,下一步還有更多東西要學(xué)。
5.預(yù)定義約束
Go 團(tuán)隊非常慷慨的為我們提供了一個常用約束的預(yù)定義包,可在 golang.org/x/exp/constraints[6] 找到。
以下是預(yù)定義約束包中包含的約束示例:
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Integer interface {
Signed | Unsigned
}
type Float interface {
~float32 | ~float64
}
type Ordered interface {
Integer | Float | ~string
}
因此,我們可以更新之前示例中的 printValue 泛型函數(shù),使其接受所有整數(shù),具體方法如下。
?? 記?。翰灰泴?dǎo)入預(yù)定義約束包 golang.org/x/exp/constraints。
重構(gòu) Leetcode 示例
現(xiàn)在我們對泛型有了一些了解,接下來重構(gòu) FindDuplicate 程序,通過泛型在整數(shù)、浮點數(shù)和字符串類型的切片及其底層類型中查找是否有重復(fù)數(shù)據(jù)。
具體修改為:
- 創(chuàng)建允許使用整數(shù)、浮點和字符串及其底層類型的接口約束
- 使用 go get 將約束包下載到項目中,在終端的 Leetcode 根目錄中執(zhí)行如下指令:
go get -u golang.org/x/exp/constraints
- 添加到項目中后,在主函數(shù)上方創(chuàng)建名為 AllowedData 的約束,如下所示:
type AllowedData interface {
constraints.Ordered
}
constraints.Ordered 是一種約束,允許任何使用支持比較運算符(如 ≤=≥===)的有序類型。
??注:可以在泛型函數(shù)中使用 constraint.Ordered,而無需創(chuàng)建新的接口約束。不過,為了便于學(xué)習(xí),我們還是創(chuàng)建了自己的約束 AllowData。
接下來,刪除類型 map 中的所有 FilterIntX 類型,創(chuàng)建一個名為 Filter 的新類型,如下所示,該類型以 T 為類型參數(shù),以 AllowedData 為約束條件:
type Filter[T AllowedData] map[T]bool
在泛型類型 Filter 前面,聲明了 T 類型參數(shù),并指定 map 鍵只接受類型參數(shù)的約束 AllowedData 作為鍵類型。
現(xiàn)在,刪除所有 FindDuplicateIntX 函數(shù)。然后使用 Go 泛型創(chuàng)建一個新的 FindDuplicate 函數(shù),代碼如下:
func FindDuplicate[T AllowedData](data []T "T AllowedData") bool {
inArray := Filter[T]{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
FindDuplicate 函數(shù)是一個泛型函數(shù),添加了類型參數(shù) T,并在函數(shù)名后面的方括號中指定了 AllowedData 約束,然后用類型參數(shù) T 定義了切片類型的函數(shù)參數(shù),并用類型參數(shù) T 初始化了 inArray。
??注:在函數(shù)中聲明泛型參數(shù)時使用方括號。
接下來,更新 has 以及 add 方法,如下所示。
func (r Filter[T]) add(datum T) {
r[datum] = true
}
func (r Filter[T]) has(datum T) bool {
_, ok := r[datum]
return ok
}
因為我們在定義類型 Filter 時已經(jīng)聲明了約束,因此方法中只包含類型參數(shù)。
最后,更新調(diào)用 FindDuplicateIntX 的調(diào)用代碼,使用新的泛型函數(shù) FindDuplicate,最終代碼如下:
package main
import (
"errors"
"fmt"
"golang.org/x/exp/constraints"
)
type Filter[T AllowedData] map[T]bool
type AllowedData interface {
constraints.Ordered
}
func main() {
data := []int{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data32 := []int32{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data64 := []int64{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
fmt.Printf("Duplicate found %t\n", FindDuplicate(data))
fmt.Printf("Duplicate found %t\n", FindDuplicate(data32))
fmt.Printf("Duplicate found %t\n", FindDuplicate(data64))
}
func (r Filter[T]) add(datum T) {
r[datum] = true
}
func (r Filter[T]) has(datum T) bool {
_, ok := r[datum]
return ok
}
func FindDuplicate[T AllowedData](data []T "T AllowedData") bool {
inArray := Filter[T]{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
現(xiàn)在執(zhí)行 go run main.go,程序成功編譯并運行,預(yù)期輸出為:
Duplicate found true
Duplicate found true
Duplicate found true
我們成功重構(gòu)了代碼,卻沒有犯復(fù)制粘貼的錯誤。
6.可比較(comparable)約束
可比較約束與相等運算符(即 == 和≠)相關(guān)聯(lián)。
這是在 Go 1.18 中引入的一個接口,由結(jié)構(gòu)體、指針、接口、管道等類似類型實現(xiàn)。
??注:Comparable 不用作任何變量的類型。
func Sort[K comparable, T Data](values map[K]T "K comparable, T Data") error {
for k, t := range values {
// code
}
return nil
}
7.約束類型鏈和類型推導(dǎo)
(1) 類型鏈
允許一個已定義的類型參數(shù)與另一個類型參數(shù)復(fù)合的做法被稱為類型鏈。當(dāng)在泛型結(jié)構(gòu)或函數(shù)中定義輔助類型時,這種方法就派上用場了。
示例:
類型鏈?zhǔn)纠?/p>
(2) 約束類型推導(dǎo)
前面我們詳細(xì)介紹了類型推導(dǎo),但與類型鏈無關(guān),可以如下調(diào)用上圖中的函數(shù):
c := Example(2)
由于 ~T 是類型參數(shù) T 與任意約束條件的復(fù)合體,因此在調(diào)用 Example 函數(shù)時可以推斷出類型參數(shù) U。
??注:2 是整數(shù),是 T 的底層類型。
8.多類型參數(shù)和約束
Go 泛型支持多類型參數(shù),但有一個問題,我們看下面的另一個例子:
package main
import "fmt"
func main() {
printValues(1, 2, 3, "c")
}
func printValues[A, B any, C comparable](a, a1 A, b B, c C "A, B any, C comparable") {
fmt.Println(a, a1, b, c)
}
如果編譯并成功運行,預(yù)期輸出結(jié)果將是:
1 2 3 c
在函數(shù)方括號[]中,我們添加了多個類型參數(shù)。類型參數(shù) A 和 B 共享同一個約束條件。在函數(shù)括號中,參數(shù) a 和 a1 共享同一個類型參數(shù) any 約束條件。
現(xiàn)在更新主函數(shù),如下所示。
...
func main() {
printValues(1, 2.1, 3, "c")
}
...
發(fā)生了什么?
我們將 2 的值從 2 改為 2.1,如你所知,這會將 2 的數(shù)據(jù)類型從 int 改為 float。當(dāng)我們再次運行程序時,編譯失?。?/p>
/main.go:6:14: default type float64 of 2.1 does not match inferred type int for A
等等!我們到底有沒有聲明 int 類型?
原因就在這里--在編譯過程中,編譯器會根據(jù)函數(shù)括號中的類型參數(shù)約束進(jìn)行推斷??梢钥吹剑琣 和 a1 共享同一個類型參數(shù) A,約束條件是 any(允許所有類型)。
編譯器會根據(jù)調(diào)用代碼的變量類型進(jìn)行推斷,并在編譯過程中使用函數(shù)括號中的類型參數(shù)約束來檢查類型。
可以看到,a 和 a1 具有相同的類型參數(shù) A,并帶有 any 約束。因此,a 和 a1 必須具有相同的類型,因為它們在用于類型推導(dǎo)的函數(shù)括號中共享相同的類型參數(shù)。
盡管類型參數(shù) A 和 B 共享同一個約束條件,但 b 在函數(shù)括號中是獨立的。
五、何時使用(或不使用)泛型
總之,請記住一點--大多數(shù)用例并不需要 Go 泛型。不過,知道什么時候需要也很有幫助,因為這樣可以大大提高工作效率。
這里有一些指導(dǎo)原則:
(1) 何時使用 Go 泛型
- 替換多個類型執(zhí)行相同邏輯的重復(fù)代碼,或者替換處理切片、映射和管道等多個類型的重復(fù)代碼
- 在處理容器型數(shù)據(jù)結(jié)構(gòu)(如鏈表、樹和堆)時
- 當(dāng)代碼邏輯需要對多種類型進(jìn)行排序、比較和/或打印時
(2) 何時不使用 Go 泛型
- 當(dāng) Go 泛型會讓代碼變得更復(fù)雜時
- 當(dāng)指定函數(shù)參數(shù)類型時
- 當(dāng)有可能濫用 Go 泛型時。避免使用 Go 泛型/類型參數(shù),除非確定有使用多種類型的重復(fù)邏輯
- 當(dāng)不同類型的實現(xiàn)不同時
- 使用 io.Reader 等讀取器時
(3) 局限性
目前,匿名函數(shù)和閉包不支持類型參數(shù)。
(4) Go 泛型的測試
由于 Go 泛型支持編寫多種類型的泛型代碼,測試用例將與函數(shù)支持的類型數(shù)量成正比增長。
六、結(jié)論
本文介紹了 Go 中的泛型、與之相關(guān)的新術(shù)語,以及如何在類型、函數(shù)、方法和結(jié)構(gòu)體中使用泛型。
希望能對大家的學(xué)習(xí) Go 有所幫助,但請不要濫用 Go 泛型。
七、收獲
如果使用得當(dāng),Go 泛型的功能會非常強大;但要謹(jǐn)慎,因為能力越大,責(zé)任越大。
Go 泛型將提高代碼的靈活性和可重用性,同時保持向后兼容,從而為 Go 語言增添價值。
它簡單易用,直接明了,學(xué)習(xí)周期短,練習(xí)有助于更好的理解 Go 泛型及其局限性。
過度使用、借用其他語言的泛型實現(xiàn)以及誤解會導(dǎo)致 Go 社區(qū)出現(xiàn)反模式和復(fù)雜性,風(fēng)險自擔(dān)。