GEEK TALK
01
前言
泛型功能是Go語言在1.18版本引入的功能,可以說是Go語言開源以來最大的語法特性變化,其改動和影響都很大, 所以整個版本的開發(fā)周期,測試周期都比以往要長很多。接下來為了大家更好的理解文章中的代碼示例,先再簡單介紹一下 Go語言在1.18版本加入的泛型的基本使用方法。
從官方的資料來看,泛型增加了三個新的重要內容:
-
將接口類型定義為類型集合,包括沒有方法的接口類型。
-
支持類型推導,大多數情況下,調用泛型函數時可省略類型實參(type arguments)。
1.1 Type Parameter
參數泛型類型(Type Parameter)可以說是泛型使用過程應用最多的場景了, 一般應用于方法或函數的形參或返回參數上。
參數泛型類型基本的使用格式可參見如下:
funcFuncName[P,Qconstraint1,Rconstraint2,...](parameter1P,parameter2Q,...)(R,Q,...)
說明: 參數泛型類定義后,可以用于函數的形參或返回參數上。
下面是一個應用參數泛型類型的代碼示例:
// Min return the min one
func Min[E int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64 | uintptr | ~string](x, y E) E {
if x < y {
return x
}
return y
}
1.2 類型集合 Type Set
類型集合是為了簡化泛型約束的使用,提升閱讀性,同時增加了復用能力,方式他通過接口定義的方式使用。
編寫格式參見如下:
type Constraint1 interface {
Type1 | ~Type2 | ...
}
以下示例定義了有符號整型與無符號整型的泛型約束:
// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
類型集合也支持繼承的方式,簡化復用。使用方式也接口的繼承是完全一致。
以下示例定義把Signed和Unsigned進行了組合,用于表達對整型泛型約束的定義。
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
1.3類型推導
引入類型推導,可以簡化我們代碼工作,目前支持2種類型推導功能。
-
通過函數的實參推導出來具體的類型以前提到的Min函數為例,可能通過傳入的傳數類型來判斷推導。
var a, b uint
minUint := Min(a, b) // 不再需要這樣寫 Min[uint](a, b)
fmt.Println(minUint)
minInt := Min(10, 100) // 常量數字,go語言會默認為 int類型,不再需要這樣寫 Min[int](a, b)
fmt.Println(minInt)
GEEK TALK
02
巧用泛型,實現(xiàn)通用排序函數
對一個數組進行排序是在業(yè)務開發(fā)中使用非常頻繁的功能,Go語言提供了sort.Sort函數,提供高效的排序功能支持,但它要求目標數組必須要實現(xiàn) sort.Interface接口。
// An implementation of Interface can be sorted by the routines in this package.
// The methods refer to elements of the underlying collection by integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with index i
// must sort before the element with index j.
//
// If both Less(i, j) and Less(j, i) are false,
// then the elements at index i and j are considered equal.
// Sort may place equal elements in any order in the final result,
// while Stable preserves the original input order of equal elements.
//
// Less must describe a transitive ordering:
// - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
// - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
//
// Note that floating-point comparison (the < operator on float32 or float64 values)
// is not a transitive ordering when not-a-number (NaN) values are involved.
// See Float64Slice.Less for a correct implementation for floating-point values.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
這樣導致我們對不同元素類型的數組,都需要重復實現(xiàn)這個接口,編寫了很多類似的代碼。下面是官方給的一個排序的示例,可以看到實現(xiàn)這樣的排序功能,要寫這么多代碼。
type Person struct {
Name string
Age int
}
// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func main() {
people := []Person{
{"Bob", 31},
{"John", 42},
{"Michael", 17},
{"Jenny", 26},
}
fmt.Println(people)
// There are two ways to sort a slice. First, one can define
// a set of methods for the slice type, as with ByAge, and
// call sort.Sort. In this first example we use that technique.
sort.Sort(ByAge(people))
fmt.Println(people)
// Output:
// [Bob: 31 John: 42 Michael: 17 Jenny: 26]
// [Michael: 17 Jenny: 26 Bob: 31 John: 42]
}
下面我們應用泛型,編寫一個通用的排序功能,可以支持任何類型的數組。大致思路是封裝一個接受任何類型(any)的結構體sortable,來實現(xiàn)sort.Interface接口,把需要擴展的部分(比較處理)進行可設置化。主要的代碼如下:
// 通用化的排序實現(xiàn)
type sortable[E any] struct {
data []E
cmp base.CMP[E]
}
func (s sortable[E]) Len() int { return len(s.data) }
func (s sortable[E]) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
func (s sortable[E]) Less(i, j int) bool {
return s.cmp(s.data[i], s.data[j]) >= 0
}
接入下來,實現(xiàn)一個支持泛型的排序函數,對任何類型的數組進行排序。
func Sort[E any](data []E, cmp base.CMP[E]) {
sortobject := sortable[E]{data: data, cmp: cmp}
sort.Sort(sortobject)
}
至此,我們就已經實現(xiàn)一個通用的排序函數了, 應用這個函數,上面官方給出的排序實現(xiàn)就可以簡化如下:
type Person struct {
Name string
Age int
}
people := []Person{
{"Bob", 31},
{"John", 42},
{"Michael", 17},
{"Jenny", 26},
}
people = Sort(people, func(e1, e2 Person) int {
return e1.Age - e2.Age
})
// Output:
// [Michael: 17 Jenny: 26 Bob: 31 John: 42]
可以看到, 應用泛型后,只需要簡單的一個函數調用就可以了。
完整的代碼實現(xiàn)可參見:https://github.com/jhunters/goassist/blob/main/arrayutil/array.go
GEEK TALK
03
巧用泛型,簡化strconv.Append系列函數
Go語言內置的strconv包的api也是日常開發(fā)經常使用的, 它提供的Append系列函數可以實現(xiàn)高效的字符串拼接功能,但因為Go語言不支持重載,所以會看到因為接受參數類型的不同,需要選擇不同的函數。
func AppendBool(dst []byte, b bool) []byte
func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte
func AppendInt(dst []byte, i int64, base int) []byte
func AppendQuote(dst []byte, s string) []byte
func AppendQuoteRune(dst []byte, r rune) []byte
func AppendQuoteRuneToASCII(dst []byte, r rune) []byte
func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte
func AppendQuoteToASCII(dst []byte, s string) []byte
func AppendQuoteToGraphic(dst []byte, s string) []byte
func AppendUint(dst []byte, i uint64, base int) []byte
所以我們不得不面臨以下使用的窘境。
// append bool
b := []byte("bool:")
b = strconv.AppendBool(b, true)
fmt.Println(string(b))
// append int
b10 := []byte("int (base 10):")
b10 = strconv.AppendInt(b10, -42, 10)
fmt.Println(string(b10))
// append quote
b := []byte("quote:")
b = strconv.AppendQuote(b, `"Fran & Freddie's Diner"`)
fmt.Println(string(b))
接下來,我們用泛型來簡化一下代碼,讓其只需要一個函數就能搞定, 直接上代碼如下:
// Append convert e to string and appends to dst
func Append[E any](dst []byte, e E) []byte {
toAppend := fmt.Sprintf("%v", e)
return append(dst, []byte(toAppend)...)
}
再來看看應用后的效果,修改之前的示例:
// append bool
b := []byte("bool:")
b = conv.Append(b, true)
fmt.Println(string(b))
// append int
b10 := []byte("int:")
b10 = conv.Append(b10, -42)
fmt.Println(string(b10))
// append quote
b = []byte("quote:")
b = conv.Append(b, `"Fran & Freddie's Diner"`)
fmt.Println(string(b))
GEEK TALK
04
巧用泛型,實現(xiàn)通用heap容器,簡化使用
Go語言container/heap包提供了一個優(yōu)先級隊列功能, 以實現(xiàn)在Pop數里時,總是優(yōu)先獲得優(yōu)先級最高的節(jié)點。
同樣的問題,如果要應用heap包的功能,針對不同的對象,必須要 實現(xiàn) heap.Interface接口, 包括5個方法。
// The Interface type describes the requirements
// for a type using the routines in this package.
// Any type that implements it may be used as a
// min-heap with the following invariants (established after
// Init has been called or if the data is empty or sorted):
//
// !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
//
// Note that Push and Pop in this interface are for package heap's
// implementation to call. To add and remove things from the heap,
// use heap.Push and heap.Pop.
type Interface interface {
sort.Interface
Push(x any) // add x as element Len()
Pop() any // remove and return element Len() - 1.
}
下面的代碼示例是來自Go語言官方,實現(xiàn)了對Int類型元素的優(yōu)先級隊列實現(xiàn):
import (
"container/heap"
"fmt"
)
// An IntHeap is a min-heap of ints.
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x any) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
func Example_intHeap() {
h := &IntHeap{2, 1, 5}
heap.Init(h)
heap.Push(h, 3)
fmt.Printf("minimum: %d
", (*h)[0])
for h.Len() > 0 {
fmt.Printf("%d ", heap.Pop(h))
}
// Output:
// minimum: 1
// 1 2 3 5
}
看到上面寫了這么多的代碼才把功能實現(xiàn), 想必大家都覺得太繁瑣了吧?那我們用泛型來改造一下,大致思路如下:
-
實現(xiàn)一個支持泛型參數的結構體heapST,實現(xiàn)heap.Interface接口。
-
開放比較函數的功能,用于使用方來更靈活的設置排序要求。
-
封裝一個全新的帶泛型參數傳入Heap結構體, 來封裝Pop與Push方法的實現(xiàn)。
主要的代碼實現(xiàn)如下:
type heapST[E any] struct {
data []E
cmp base.CMP[E]
}
// implments the methods for "heap.Interface"
func (h *heapST[E]) Len() int { return len(h.data) }
func (h *heapST[E]) Less(i, j int) bool {
v := h.cmp(h.data[i], h.data[j])
return v < 0
}
func (h *heapST[E]) Swap(i, j int) { h.data[i], h.data[j] = h.data[j], h.data[i] }
func (h *heapST[E]) Push(x any) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
v := append(h.data, x.(E))
h.data = v
}
func (h *heapST[E]) Pop() any {
old := h.data
n := len(old)
x := old[n-1]
h.data = old[0 : n-1]
return x
}
// Heap base on generics to build a heap tree for any type
type Heap[E any] struct {
data *heapST[E]
}
// Push pushes the element x onto the heap.
// The complexity is O(log n) where n = h.Len().
func (h *Heap[E]) Push(v E) {
heap.Push(h.data, v)
}
// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to Remove(h, 0).
func (h *Heap[E]) Pop() E {
return heap.Pop(h.data).(E)
}
func (h *Heap[E]) Element(index int) (e E, err error) {
if index < 0 || index >= h.data.Len() {
return e, fmt.Errorf("out of index")
}
return h.data.data[index], nil
}
// Remove removes and returns the element at index i from the heap.
// The complexity is O(log n) where n = h.Len().
func (h *Heap[E]) Remove(index int) E {
return heap.Remove(h.data, index).(E)
}
func (h *Heap[E]) Len() int {
return len(h.data.data)
}
// Copy to copy heap
func (h *Heap[E]) Copy() *Heap[E] {
ret := heapST[E]{cmp: h.data.cmp}
ret.data = make([]E, len(h.data.data))
copy(ret.data, h.data.data)
heap.Init(&ret)
return &Heap[E]{&ret}
}
// NewHeap return Heap pointer and init the heap tree
func NewHeap[E any](t []E, cmp base.CMP[E]) *Heap[E] {
ret := heapST[E]{data: t, cmp: cmp}
heap.Init(&ret)
return &Heap[E]{&ret}
}
完整的代碼獲取:https://github.com/jhunters/goassist/blob/main/container/heapx/heap.go
接入來可以改寫之前的代碼, 代碼如下:
// An IntHeap is a min-heap of ints.
type IntHeap []int
// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
func Example_intHeap() {
h := heapx.NewHeap(IntHeap{2, 1, 5}, func(p1, p2 int) int {
return p1 - p2
})
h.Push(3)
for h.Len() > 0 {
fmt.Printf("%d ", h.Pop())
}
// Output:
// 1 2 3 5
}
可以看到改寫后,代碼量大量減少,而且代碼的可讀性也大大提升. 完整的使用示例可參見:https://github.com/jhunters/goassist/blob/main/container/heapx/heap_test.go
GEEK TALK
05
巧用泛型,提升Pool容器可讀性與安全性
Go語言內存的sync包下Pool對象, 提供了可伸縮、并發(fā)安全的臨時對象池的功能,用來存放已經分配但暫時不用的臨時對象,通過對象重用機制,緩解 GC 壓力,提高程序性能。需要注意的是Pool 是一個臨時對象池,適用于儲存一些會在 goroutine 間共享的臨時對象,其中保存的任何項都可能隨時不做通知地釋放掉,所以不適合當于緩存或對象池的功能。
Pool的框架代碼如下:
type Pool struct {
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() interface{}
// contains filtered or unexported fields
}
// Get 從 Pool 中獲取元素。當 Pool 中沒有元素時,會調用 New 生成元素,新元素不會放入 Pool 中。若 New 未定義,則返回 nil。
func (p *Pool) Get() interface{}
// Put 往 Pool 中添加元素 x。
func (p *Pool) Put(x interface{})
官方Pool的API使用起來已經是非常方便,下面是摘取官方文檔中的示例代碼:
package sync_test
import (
"bytes"
"io"
"os"
"sync"
"time"
)
var bufPool = sync.Pool{
New: func() any {
// The Pool's New function should generally only return pointer
// types, since a pointer can be put into the return interface
// value without an allocation:
return new(bytes.Buffer)
},
}
// timeNow is a fake version of time.Now for tests.
func timeNow() time.Time {
return time.Unix(1136214245, 0)
}
func Log(w io.Writer, key, val string) {
b := bufPool.Get().(*bytes.Buffer)
b.Reset()
// Replace this with time.Now() in a real logger.
b.WriteString(timeNow().UTC().Format(time.RFC3339))
b.WriteByte(' ')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(val)
w.Write(b.Bytes())
bufPool.Put(b)
}
func ExamplePool() {
Log(os.Stdout, "path", "/search?q=flowers")
// Output: 2006-01-02T1505Z path=/search?q=flowers
}
從上面的代碼,可以看到一個問題就是從池中獲取對象時,要強制進行轉換,如果轉換類型不匹配,就會出現(xiàn)Panic異常,這種場景正是泛型可以很好解決的場景,我們改造代碼如下, 封裝一個全新的帶泛型參數傳入 Pool 結構體:
package syncx
import (
"sync"
"github.com/jhunters/goassist/base"
)
type Pool[E any] struct {
New base.Supplier[E]
internal sync.Pool
}
// NewPoolX create a new PoolX
func NewPool[E any](f base.Supplier[E]) *Pool[E] {
p := Pool[E]{New: f}
p.internal = sync.Pool{
New: func() any {
return p.New()
},
}
return &p
}
// Get selects an E generic type item from the Pool
func (p *Pool[E]) Get() E {
v := p.internal.Get()
return v.(E)
}
// Put adds x to the pool.
func (p *Pool[E]) Put(v E) {
p.internal.Put(v)
}
接下來,使用新封裝的Pool對象改寫上面的官方示例代碼:
var bufPool = syncx.NewPool(func() *bytes.Buffer {
return new(bytes.Buffer)
})
// timeNow is a fake version of time.Now for tests.
func timeNow() time.Time {
return time.Unix(1136214245, 0)
}
func Log(w io.Writer, key, val string) {
b := bufPool.Get() // 不再需要強制類型轉換
b.Reset()
// Replace this with time.Now() in a real logger.
b.WriteString(timeNow().UTC().Format(time.RFC3339))
b.WriteByte(' ')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(val)
w.Write(b.Bytes())
bufPool.Put(b)
}
func ExamplePool() {
Log(os.Stdout, "path", "/search?q=flowers")
// Output: 2006-01-02T1505Z path=/search?q=flowers
}
完整的代碼實現(xiàn)與使用示例可參見:https://github.com/jhunters/goassist/tree/main/concurrent/syncx
GEEK TALK
06
巧用泛型,增強sync.Map容器功能
sync.Map是Go語言官方提供的一個map映射的封裝實現(xiàn),提供了一些更實用的方法以更方便的操作map映射,同時它本身也是線程安全的,包括原子化的更新支持。
type Map
func (m *Map) Delete(key any)
func (m *Map) Load(key any) (value any, ok bool)
func (m *Map) LoadAndDelete(key any) (value any, loaded bool)
func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)
func (m *Map) Range(f func(key, value any) bool)
func (m *Map) Store(key, value any)
接入來我們要用泛型功能,給sync.Map增加如下功能:
-
所有的操作支持泛型,以省去對象強制轉換功能
-
引入泛型后,保障了key與value類型的一致性,可以擴展支持 Key或Value是否存在, 查詢最小最大Key或Value的功能
-
另外還增加了StoreAll 從另一個map導入, ToMap轉成原生map結構, Clear清空map, 以數組結構導出key或value等實用功能
增加后Map的API列表如下:
type Map
func NewMap[K comparable, V any]() *Map[K, V]
func (m *Map[K, V]) Clear()
func (m *Map[K, V]) Copy() *Map[K, V]
func (m *Map[K, V]) Exist(key K) bool
func (m *Map[K, V]) ExistValue(value V) (k K, exist bool)
func (m *Map[K, V]) ExistValueWithComparator(value V, equal base.EQL[V]) (k K, exist bool)
func (m *Map[K, V]) Get(key K) (V, bool)
func (m *Map[K, V]) IsEmpty() (empty bool)
func (m *Map[K, V]) Keys() []K
func (m *Map[K, V]) MaxKey(compare base.CMP[K]) (key K, v V)
func (m *Map[K, V]) MaxValue(compare base.CMP[V]) (key K, v V)
func (m *Map[K, V]) MinKey(compare base.CMP[K]) (key K, v V)
func (m *Map[K, V]) MinValue(compare base.CMP[V]) (key K, v V)
func (m *Map[K, V]) Put(key K, value V) V
func (m *Map[K, V]) Range(f base.BiFunc[bool, K, V])
func (m *Map[K, V]) Remove(key K) bool
func (m *Map[K, V]) Size() int
func (m *Map[K, V]) ToMap() map[K]V
func (m *Map[K, V]) Values() []V
完整的API列表在此閱讀:http://localhost:4040/pkg/github.com/jhunters/goassist/container/mapx/
部分泛型代碼后的代碼如下:
// Map is like a Go map[interface{}]interface{} but is safe for concurrent use
// by multiple goroutines without additional locking or coordination.
// Loads, stores, and deletes run in amortized constant time.
// By generics feature supports, all api will be more readable and safty.
//
// The Map type is specialized. Most code should use a plain Go map instead,
// with separate locking or coordination, for better type safety and to make it
// easier to maintain other invariants along with the map content.
//
// The Map type is optimized for two common use cases: (1) when the entry for a given
// key is only ever written once but read many times, as in caches that only grow,
// or (2) when multiple goroutines read, write, and overwrite entries for disjoint
// sets of keys. In these two cases, use of a Map may significantly reduce lock
// contention compared to a Go map paired with a separate Mutex or RWMutex.
//
// The zero Map is empty and ready for use. A Map must not be copied after first use.
type Map[K comparable, V any] struct {
mp sync.Map
empty V
mu sync.Mutex
}
// NewMap create a new map
func NewMap[K comparable, V any]() *Map[K, V] {
return &Map[K, V]{mp: sync.Map{}}
}
// NewMapByInitial create a new map and store key and value from origin map
func NewMapByInitial[K comparable, V any](mmp map[K]V) *Map[K, V] {
mp := NewMap[K, V]()
if mmp == nil {
return mp
}
for k, v := range mmp {
mp.Store(k, v)
}
return mp
}
// Exist return true if key exist
func (m *Map[K, V]) Exist(key K) bool {
_, ok := m.mp.Load(key)
return ok
}
// ExistValue return true if value exist
func (m *Map[K, V]) ExistValue(value V) (k K, exist bool) {
de := reflectutil.NewDeepEquals(value)
m.Range(func(key K, val V) bool {
if de.Matches(val) {
exist = true
k = key
return false
}
return true
})
return
}
// ExistValue return true if value exist
func (m *Map[K, V]) ExistValueWithComparator(value V, equal base.EQL[V]) (k K, exist bool) {
m.Range(func(key K, val V) bool {
if equal(value, val) {
exist = true
k = key
return false
}
return true
})
return
}
// ExistValue return true if value exist
func (m *Map[K, V]) ExistValueComparable(v base.Comparable[V]) (k K, exist bool) {
m.Range(func(key K, val V) bool {
if v.CompareTo(val) == 0 {
exist = true
k = key
return false
}
return true
})
return
}
// MinValue to return min value in the map
func (m *Map[K, V]) MinValue(compare base.CMP[V]) (key K, v V) {
return selectByCompareValue(m, func(o1, o2 V) int {
return compare(o1, o2)
})
}
// MaxValue to return max value in the map
func (m *Map[K, V]) MaxValue(compare base.CMP[V]) (key K, v V) {
return selectByCompareValue(m, func(o1, o2 V) int {
return compare(o2, o1)
})
}
// MinKey to return min key in the map
func (m *Map[K, V]) MinKey(compare base.CMP[K]) (key K, v V) {
return selectByCompareKey(m, func(o1, o2 K) int {
return compare(o1, o2)
})
}
// MaxKey to return max key in the map
func (m *Map[K, V]) MaxKey(compare base.CMP[K]) (key K, v V) {
return selectByCompareKey(m, func(o1, o2 K) int {
return compare(o2, o1)
})
}
完整的代碼與使用示例參見:[1]https://github.com/jhunters/goassist/blob/main/concurrent/syncx/map.go[2]https://github.com/jhunters/goassist/blob/main/concurrent/syncx/example_map_test.go
GEEK TALK
07
總結
應用泛型可以極大的減少代碼的編寫量,同時提升可讀性與安全性都有幫助,上面提到的對于泛型的使用技巧只能是很少一部分,還有更多的case等待大家來發(fā)現(xiàn)。另外對泛型感興趣的同學,也推薦大家閱讀這個開源項目 https://github.com/jhunters/goassist 里面有非常多經典的泛型使用技巧,相信對大家理解與掌握泛型會有很多幫助。
-
函數
+關注
關注
3文章
4332瀏覽量
62666 -
代碼
+關注
關注
30文章
4790瀏覽量
68654 -
數組
+關注
關注
1文章
417瀏覽量
25960
原文標題:巧用Golang泛型,簡化代碼編寫
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論