Heim >Backend-Entwicklung >Golang >Generics in Go: Transformieren der Wiederverwendbarkeit von Code
Generika, eingeführt in Go 1.18, haben die Art und Weise, wiederverwendbaren und typsicheren Code zu schreiben, revolutioniert. Generika bringen Flexibilität und Leistung und wahren gleichzeitig Gos Philosophie der Einfachheit. Um jedoch die Nuancen, Vorteile und den Vergleich von Generika mit herkömmlichen Ansätzen (wie interface{} ) zu verstehen, ist ein genauerer Blick erforderlich.
Lassen Sie uns die Feinheiten von Generika erkunden, uns mit Einschränkungen befassen, Generika mit Schnittstellen vergleichen{} und ihre praktischen Anwendungen demonstrieren. Wir werden auch auf Leistungsüberlegungen und Auswirkungen auf die Binärgröße eingehen. Lasst uns eintauchen!
Generika ermöglichen es Entwicklern, Funktionen und Datenstrukturen zu schreiben, die auf jedem Typ ausgeführt werden können und gleichzeitig die Typsicherheit wahren. Anstatt sich auf interface{} zu verlassen, das Typzusicherungen zur Laufzeit beinhaltet, können Sie mit Generics eine Reihe von Einschränkungen angeben, die die zulässigen Operationen für die Typen vorgeben.
Syntax
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
T: Ein Typparameter, der einen Platzhalter für den Typ darstellt.
TypeConstraint: Beschränkt den Typ von T auf einen bestimmten Typ oder eine Reihe von Typen.
parameterName T: Der Parameter verwendet den generischen Typ T.
ReturnType: Die Funktion kann auch einen Wert vom Typ T.
zurückgebenBeispiel
func Sum[T int | float64](a, b T) T { return a + b }
func Sum: Deklariert den Namen der Funktion, Sum
[T int | float64]: Gibt eine Typparameterliste an, die T als Typparameter einführt, beschränkt auf bestimmte Typen (int oder float64). Die Summenfunktion kann nur Parameter entweder int oder float64 annehmen, nicht in Kombination, beide müssen entweder int oder float64 sein. Wir werden dies in den folgenden Abschnitten näher untersuchen.
(a, b T): Deklariert zwei Parameter, a und b, beide vom Typ T (der generische Typ). ).
T: Gibt den Rückgabetyp der Funktion an, der dem Typparameter T.
entsprichtEinschränkungen definieren, welche Operationen für einen generischen Typ gültig sind. Go bietet leistungsstarke Tools für Einschränkungen, einschließlich des Pakets für experimentelle Einschränkungen (golang.org/x/exp/constraints).
Go hat integrierte Einschränkungen mit Generika eingeführt, um Typsicherheit zu gewährleisten und gleichzeitig Flexibilität bei der Definition von wiederverwendbarem und generischem Code zu ermöglichen. Diese Einschränkungen ermöglichen es Entwicklern, Regeln für die Typen durchzusetzen, die in generischen Funktionen oder Typen verwendet werden.
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
func Sum[T int | float64](a, b T) T { return a + b }
Experimentelle Einschränkungen
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
Benutzerdefinierte Einschränkungen sind Schnittstellen, die eine Reihe von Typen oder Typverhalten definieren, die ein generischer Typparameter erfüllen muss. Indem wir Ihre eigenen Einschränkungen erstellen, können wir;
Typen auf eine bestimmte Teilmenge beschränken, z. B. numerische Typen.
Erfordern Typen, um bestimmte Methoden oder Verhaltensweisen zu implementieren.
Fügen Sie Ihren generischen Funktionen und Typen mehr Kontrolle und Spezifität hinzu.
Syntax
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
Beispiel
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
Summenfunktion kann nur mit den Parametern int, int64 und float64 aufgerufen werden.
Wenn Sie erzwingen möchten, dass ein Typ bestimmte Methoden implementieren muss, können Sie ihn mithilfe dieser Methoden definieren.
type Numeric interface { int | float64 | uint }
Die Einschränkung Formatter erfordert, dass jeder Typ, der als T verwendet wird, über eine Formatmethode verfügen muss, die ein string.
Einschränkungen kombinieren
type Number interface { int | int64 | float64 } func Sum[T Number](a, b T) T { return a + b }Diese Einschränkung umfasst beide spezifischen Typen (
int, float54) und erfordert das Vorhandensein einer abs-Methode.
Generics vs. Schnittstelle{}Typensicherheit
Schnittstelle{}: Langsamer aufgrund zusätzlicher Laufzeittypprüfungen.
Generika: Schneller, da der Compiler optimierte Codepfade speziell für Typen generiert.
Schnittstelle{}: Oft ausführlich und weniger intuitiv, was die Wartung des Codes erschwert.
Generika: Eine sauberere Syntax führt zu intuitiverem und wartbarerem Code.
Schnittstelle{}: Ergibt kleinere Binärdateien, da kein Code für verschiedene Typen dupliziert wird.
Generika: Erhöht die Binärgröße aufgrund der Typspezialisierung leicht für eine bessere Leistung.
Beispiel
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
Code funktioniert gut, die Typzusicherung ist Overhead. Die Add-Funktion kann mit jedem Argument aufgerufen werden. Sowohl a- als auch b-Parameter können von unterschiedlichem Typ sein, allerdings stürzt der Code zur Laufzeit ab.
func Sum[T int | float64](a, b T) T { return a + b }
Generika eliminieren das Risiko von Laufzeitpaniken, die durch falsche Typzusicherungen verursacht werden, und verbessern die Klarheit.
Generika erzeugen für jeden Typ speziellen Code, was zu einer besseren Laufzeitleistung im Vergleich zur Schnittstelle{} führt.
Es gibt einen Kompromiss: Generika erhöhen die Binärgröße aufgrund der Codeduplizierung für jeden Typ, aber dies ist im Vergleich zu den Vorteilen oft vernachlässigbar.
Komplexität in Einschränkungen: Während Einschränkungen wie Constraints.Ordered häufige Anwendungsfälle vereinfachen, kann die Definition stark angepasster Einschränkungen ausführlich werden.
Keine Typinferenz in Strukturen: Im Gegensatz zu Funktionen müssen Sie den Typparameter für Strukturen explizit angeben.
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
Beschränkt auf Einschränkungen zur Kompilierungszeit: Go-Generika konzentrieren sich auf die Sicherheit zur Kompilierungszeit, während Sprachen wie Rust stärkere Einschränkungen mithilfe von Lebensdauern und Merkmalen bieten.
Wir werden eine einfache Warteschlange sowohl mit Schnittstelle{} als auch generisch implementieren und die Ergebnisse einem Benchmarking unterziehen.
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
type Numeric interface { int | float64 | uint }
Ausführungszeit:
Die generische Implementierung ist etwa 63,64 % schneller als die Interface{}-Version, da sie Laufzeittypzusicherungen vermeidet und direkt mit dem angegebenen Typ arbeitet.
Zuweisungen:
Die interface{}-Version nimmt dreimal mehr Zuweisungen vor, hauptsächlich aufgrund des Ein-/Auspackens beim Einfügen und Abrufen von Werten. Dies erhöht den Aufwand für die Speicherbereinigung.
Bei größeren Arbeitslasten, z. B. 1 Million Enqueue-/Dequeue-Vorgängen, vergrößert sich die Leistungslücke. Reale Anwendungen mit hohen Durchsatzanforderungen (z. B. Nachrichtenwarteschlangen, Jobplaner) profitieren erheblich von Generika.
Generika in Go schaffen ein Gleichgewicht zwischen Leistung und Einfachheit und bieten eine praktische Lösung zum Schreiben von wiederverwendbarem und typsicherem Code. Obwohl es nicht so funktionsreich ist wie Rust oder C, passt es perfekt zur minimalistischen Philosophie von Go. Das Verstehen von Einschränkungen wie Constraints.Ordered und die effektive Nutzung von Generika können die Codequalität und Wartbarkeit erheblich verbessern.
Da sich Generika ständig weiterentwickeln, werden sie eine zentrale Rolle im Ökosystem von Go spielen. Also tauchen Sie ein, experimentieren Sie und begrüßen Sie die neue Ära der Typsicherheit und Flexibilität in der Go-Programmierung!
Schauen Sie sich das Github-Repository an, um einige Beispiele zu Generika zu finden.
Willkommen im Go Generics Repository! Dieses Repository ist eine zentrale Ressource zum Verstehen, Erlernen und Beherrschen von Generika in Go, eingeführt in Version 1.18. Generics bringen die Leistungsfähigkeit von Typparametern in Go und ermöglichen es Entwicklern, wiederverwendbaren und typsicheren Code zu schreiben, ohne Kompromisse bei der Leistung oder Lesbarkeit einzugehen.
Dieses Repository enthält sorgfältig kuratierte Beispiele, die ein breites Themenspektrum abdecken, von grundlegender Syntax bis hin zu fortgeschrittenen Mustern und praktischen Anwendungsfällen. Egal, ob Sie Anfänger oder erfahrener Go-Entwickler sind, diese Sammlung hilft Ihnen dabei, Generika effektiv in Ihren Projekten zu nutzen.
Diese Beispiele stellen die grundlegenden Konzepte von Generika vor und helfen Ihnen, die Syntax und Kernfunktionen zu verstehen:
Das obige ist der detaillierte Inhalt vonGenerics in Go: Transformieren der Wiederverwendbarkeit von Code. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!