This article brings you an in-depth understanding of generics in golang? How to use generics? It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
What is generics
Generics (Generic) is a programming technology. In a strongly typed language, you are allowed to write code using types that are specified later, and specify the corresponding type at instantiation time.
In generics, type parameters can be used instead of specific data types. These type parameters can be declared in a class, method, or interface, and can be used in these declarations. Code that uses generics can specify the actual type parameters at runtime, which allows the code to be applied to many different types of data.
Generics can improve the readability, maintainability and reusability of code. It reduces the redundancy of your code and provides better type safety and compile-time type checking.
Let’s use a specific example to explain why generics can reduce code redundancy:
Provide a function that returns the minimum value of a and b. We need each Write a function for a specific data type "int, float..."; or use interface{} "It requires type assertion for parameters, which has an impact on running performance, and cannot constrain the parameters passed in"
func minInt(a, b int) int { if a > b { return b } return a } func minFloat(a, b float64) float64 { if a > b { return b } return a } func minItf(a, b interface{}) interface{} { switch a.(type) { case int: switch b.(type) { case int: if a.(int) > b.(int) { return b } return a } case float64: switch b.(type) { case float64: if a.(float64) > b.(float64) { return b } return a } } return nil }
We can see from the above method that except for the different types of parameters and return results, minInt and minFloat have the same code. Is there a way to determine the type passed in when the function is called without specifying a specific type? Here, a concept called generics is introduced, which can be simply understood as a broad type or an unspecified specific type. By introducing generics, we no longer need to specify specific data types. The min function can be used in the following way:
// T 為類型參數(shù), 在調(diào)用時(shí)確定參數(shù)的具體值, 可以為 int, 也可以為 float64;它與 a, b 一樣也是參數(shù), 需要調(diào)用時(shí)傳入具體的值;不同的是,T 為類型參數(shù),值為具體的類型, a,b 為函數(shù)參數(shù),值為具體類型對(duì)應(yīng)的值 func minIntAndFloat64[T int | float64](a, b T) T { if a < b { return a } return b } minIntAndFloat64[int](1, 2) // 實(shí)例化/調(diào)用時(shí)指定具體的類型
Generics in go
go only introduced generics in version 1.8. If your go version is lower than 1.8, you cannot use generics. The code in this article uses version 1.9. In version 1.8, a lot of changes were made to support generics.
- Type parameters are introduced in function and type declarations
- A collection of types can be defined through interfaces, including types without methods
- Type derivation, in some scenarios Type parameters will be deduced, and you can call the function without specifying the value of the type parameter
Formal parameters, actual parameters, type parameters, type actual parameters, instantiation
Let’s look at a common add
function first. add
is the function name, x, y
is the formal parameter, (x, y int)
is the parameter list. When a function call occurs, add(2, 3)
2, 3 are actual parameters.
By analogy to generics, we need a type parameter. When a function call occurs, the corresponding type parameter is passed in. A function with type parameters is called a generic function.
[T int | int64]
is the type parameter list, T
is the type parameter, int | int64
is the type set/type constraint. When a function call occurs add[int](2,3)
, int is the type actual parameter. This call is also called instantiation, that is, the type actual parameter is determined.
Type parameters can also be specified when declaring the structure. MyStruct[T]
is a generic structure, and methods can be defined for generic structures.
Type collection, interface
In the basic type, uint8 represents a collection of 0~255. Then for type parameters, you also need to define a collection of types just like the basic type. In the above example int | string
is a collection of types. So how to reuse a collection of types? Interfaces are used here for definition. The following is the definition of a type collection. Therefore, we can define a generic function add[T Signed](x, y T) T
Before go 1.8, the definition of the interface It is a collection of methods, that is, if the method corresponding to the interface is implemented, it can be converted into the corresponding interface. In the following example, the MyInt
type implements the Add method and can therefore be converted to MyInterface
.
type MyInterface interface { Add(x, y int) int } type MyInt int func (mi myInt) Add(x, y int) int { return x + y } func main() { var mi MyInterface = myInt(1) fmt.Println(mi.Add(1, 2)) }
If we think about it from another angle, MyInterface
can be regarded as a type collection, which includes all types that implement the add
method. Then, MyInterface can be used as a collection of types. For example, we can define a generic function as follows.
func I[T MyInterface](x, y int, i T) int { return i.Add(x, y) }
在泛型中, 我們的類型集合不僅僅是實(shí)現(xiàn)接口中定義方法的類型, 還需要包含基礎(chǔ)的類型。因此, 我們可以對(duì)接口的定義進(jìn)行延伸, 使其支持基礎(chǔ)類型。為了保證向前兼容, 我們需要對(duì)接口類型進(jìn)行分類:
基礎(chǔ)接口類型
只包含方法的集合, 既可以當(dāng)作類型集合, 又可以作為數(shù)據(jù)類型進(jìn)行聲明。如下面的 MyInterface
。還有一個(gè)特殊的接口類型 interface{}, 它可以用來(lái)表示任意類型, 即所有的類型都實(shí)現(xiàn)了它的空方法。在 1.8 之后可以使用 any 進(jìn)行聲明。
type any = interface{} type MyInterface interface { Add(x, y int) int String() string String() string // 非法: String 不能重復(fù)聲明 _(x int) // 非法: 必須要有一個(gè)非空的名字 }
接口組合
可以通過(guò)接口組合的形式聲明新的接口, 從而盡可能的復(fù)用接口。從下面的例子可以看出, ReadWriter
是 Reader
和 Write
的類型集合的交集。
type Reader interface { Read(p []byte) (n int, err error) Close() error } type Writer interface { Write(p []byte) (n int, err error) Close() error } // ReadWriter's methods are Read, Write, and Close. type ReadWriter interface { Reader // includes methods of Reader in ReadWriter's method set Writer // includes methods of Writer in ReadWriter's method set }
通用接口
上面說(shuō)的接口都必須要實(shí)現(xiàn)具體的方法, 但是類型集合中無(wú)法包含基礎(chǔ)的數(shù)據(jù)類型。如: int, float, string...。通過(guò)下面的定義, 可以用來(lái)表示包含基礎(chǔ)數(shù)據(jù)類型的類型集合。在 golang.org/x/exp/constraints
中定義了基礎(chǔ)數(shù)據(jù)類型的集合。我們可以看到 ~
符號(hào), 它表示包含潛在類型為 int | int8 | int16 | int32 | int64 的類型, |
表示取并集。Singed
就表示所有類型為 int 的類型集合。
// 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 } type myInt int // 潛在類型為 int func add[T constraints.Integer](x, y T) T { return x + y } func main() { var x, y myInt = 1, 2 fmt.Println(add[myInt](x, y)) }
下面來(lái)看一些特殊的定義
// 潛在類型為 int, 并且實(shí)現(xiàn)了 String 方法的類型 type E interface { ~int String() string } type mInt int // 屬于 E 的類型集合 func (m mInt) String() string { return fmt.Sprintf("%v", m) } // 潛在類型必須是自己真實(shí)的類型 type F interface { ~int // ~mInt invalid use of ~ (underlying type of mInt is int) // ~error illegal: error is an interface } // 基礎(chǔ)接口可以作為形參和類型參數(shù)類型, 通用類型只能作為類型參數(shù)類型, E 只能出現(xiàn)在類型參數(shù)中 [T E] var x E // illegal: cannot use type E outside a type constraint: interface contains type constraints var x interface{} = E(nil) // illegal: cannot use interface E in conversion (contains specific type constraints or is comparable)
類型推導(dǎo)
由于泛型使用了類型參數(shù), 因此在實(shí)例化泛型時(shí)我們需要指定類型實(shí)參。 看下面的 case, 我們?cè)谡{(diào)用函數(shù)的時(shí)候并沒有指定類型實(shí)參, 這里是編譯器進(jìn)行了類型推導(dǎo), 推導(dǎo)出類型實(shí)參, 不需要顯性的傳入。
func add[T constraints.Integer](x, y T) T { return x + y } func main() { fmt.Println(add(1, 1)) // add[int](1,1) }
有時(shí)候, 編譯器無(wú)法推導(dǎo)出具體類型。則需要指定類型, 或者更換寫法, 也許可以推斷出具體類型。
// 將切片中的值擴(kuò)大 func Scale[E constraints.Integer](s []E, c E) []E { r := make([]E, len(s)) for i, v := range s { r[i] = v * c } return r } func ScaleAndPrint(p Point) { r := Scale(p, 2) r.string() // 非法, Scale 返回的是 []int32 } type Point []int32 func (p Point) string() { fmt.Println(p) } // 方法更新,這樣傳入的是 Point 返回的也是 Point func Scale[T ~[]E, E constraints.Integer](s T, c E) T { r := make([]E, len(s)) for i, v := range s { r[i] = v * c } return r }
泛型的使用
go 是在 1.8 版本中開始引入泛型的。下面主要介紹一下什么時(shí)候使用泛型:
內(nèi)置容器類型
在 go 中, 提供以下容器類型:map, slice, channel。當(dāng)我們用到容器類型時(shí), 且邏輯與容器具體的類型無(wú)關(guān), 這個(gè)時(shí)候可以考慮泛型。這樣我們可以在調(diào)用時(shí)指定具體的類型實(shí)參, 從而避免了類型斷言。例如,下面的例子, 返回 map 中的 key。
// comparable 是一個(gè)內(nèi)置類型, 只能用于對(duì)類型參數(shù)的約束。在 map 中, key 必須是可比較類型。 func GetKeys[K comparable, V any](m map[K]V) []K { res := make([]K, 0, len(m)) for k := range m { res = append(res, k) } return res }
通用的結(jié)構(gòu)體
對(duì)于一些通用的結(jié)構(gòu)體, 我們應(yīng)該使用泛型。例如, 棧、隊(duì)列、樹結(jié)構(gòu)。這些都是比較通用的結(jié)構(gòu)體, 且邏輯都與具體的類型無(wú)關(guān), 因此需要使用泛型。下面是一個(gè)棧的例子:
type Stack[T any] []T func (s *Stack[T]) Push(item T) { *s = append(*s, item) } func (s *Stack[T]) Pop() T { if len(*s) == 0 { panic("can not pop item in emply stack") } lastIndex := len(*s) - 1 item := (*s)[lastIndex] *s = (*s)[:lastIndex] return item } func main() { var s Stack[int] s.Push(9) fmt.Println(s.Pop()) s.Push(9) s.Push(8) fmt.Println(s.Pop(), s.Pop()) }
通用的函數(shù)
有些類型會(huì)實(shí)現(xiàn)相同的方法, 但是對(duì)于這些類型的處理邏輯又與具體類型的實(shí)現(xiàn)無(wú)關(guān)。例如: 兩個(gè)數(shù)比大小, 只要實(shí)現(xiàn) Ordered 接口即可進(jìn)行大小比較:
func Min[T constraints.Ordered](x, y T) T { if x < y { return x } return y } func main() { fmt.Println(Min(5, 6)) fmt.Println(Min(6.6, 9.9)) }
總結(jié)
go 在引入泛型算是一次較大的改動(dòng)。我們只有弄清楚類型參數(shù)、類型約束、類型集合、基礎(chǔ)接口、通用接口、泛型函數(shù)、泛型類型、泛型接口等概念, 才能不會(huì)困惑。核心改動(dòng)點(diǎn)還是引入了類型參數(shù), 使用接口來(lái)定義類型集合。
當(dāng)然,也不能為了使用泛型而使用泛型。還是要具體的 case 具體來(lái)分析。 簡(jiǎn)單的指導(dǎo)原則就是, 當(dāng)你發(fā)現(xiàn)你的代碼除了類型不同外, 其余代碼邏輯都相同; 或者你寫了許多重復(fù)代碼, 僅僅是為了支持不同類型; 那么你可以考慮使用泛型。
推薦學(xué)習(xí):Golang教程
The above is the detailed content of In-depth understanding of generics in golang (Generic). For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Go's switch statement will not be executed throughout the process by default and will automatically exit after matching the first condition. 1. Switch starts with a keyword and can carry one or no value; 2. Case matches from top to bottom in order, only the first match is run; 3. Multiple conditions can be listed by commas to match the same case; 4. There is no need to manually add break, but can be forced through; 5.default is used for unmatched cases, usually placed at the end.

In Go, to break out of nested loops, you should use labeled break statements or return through functions; 1. Use labeled break: Place the tag before the outer loop, such as OuterLoop:for{...}, use breakOuterLoop in the inner loop to directly exit the outer loop; 2. Put the nested loop into the function, and return in advance when the conditions are met, thereby terminating all loops; 3. Avoid using flag variables or goto, the former is lengthy and easy to make mistakes, and the latter is not recommended; the correct way is that the tag must be before the loop rather than after it, which is the idiomatic way to break out of multi-layer loops in Go.

Usecontexttopropagatecancellationanddeadlinesacrossgoroutines,enablingcooperativecancellationinHTTPservers,backgroundtasks,andchainedcalls.2.Withcontext.WithCancel(),createacancellablecontextandcallcancel()tosignaltermination,alwaysdeferringcancel()t

InitializeaGomodulewithgomodinit,2.InstallgqlgenCLI,3.Defineaschemainschema.graphqls,4.Rungqlgeninittogeneratemodelsandresolvers,5.Implementresolverfunctionsforqueriesandmutations,6.SetupanHTTPserverusingthegeneratedschema,and7.RuntheservertoaccessGr

Gooffersfasterexecutionspeedduetocompilationtonativemachinecode,outperforminginterpretedlanguageslikePythonintaskssuchasservingHTTPrequests.2.Itsefficientconcurrencymodelusinglightweightgoroutinesenablesthousandsofconcurrentoperationswithlowmemoryand

Use a dedicated and reasonably configured HTTP client to set timeout and connection pools to improve performance and resource utilization; 2. Implement a retry mechanism with exponential backoff and jitter, only retry for 5xx, network errors and 429 status codes, and comply with Retry-After headers; 3. Use caches for static data such as user information (such as sync.Map or Redis), set reasonable TTL to avoid repeated requests; 4. Use semaphore or rate.Limiter to limit concurrency and request rates to prevent current limit or blocking; 5. Encapsulate the API as an interface to facilitate testing, mocking, and adding logs, tracking and other middleware; 6. Monitor request duration, error rate, status code and retry times through structured logs and indicators, combined with Op

Use the template.ParseFS and embed package to compile HTML templates into binary files. 1. Import the embed package and embed the template file into the embed.FS variable with //go:embedtemplates/.html; 2. Call template.Must(template.ParseFS(templateFS,"templates/.html")))) to parse all matching template files; 3. Render the specified in the HTTP processor through tmpl.ExecuteTemplate(w,"home.html", nil)

To correctly copy slices in Go, you must create a new underlying array instead of directly assigning values; 1. Use make and copy functions: dst:=make([]T,len(src));copy(dst,src); 2. Use append and nil slices: dst:=append([]T(nil),src...); both methods can realize element-level copying, avoid sharing the underlying array, and ensure that modifications do not affect each other. Direct assignment of dst=src will cause both to refer to the same array and are not real copying.
