Einführung in Generics
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 67 von 107.
Vor Go 1.18 bedeutete das Schreiben von wiederverwendbarem Code für verschiedene Typen entweder, Funktionen zu duplizieren oder die leere Schnittstelle any mit Typ-Assertions zu verwenden. Generics lösen dieses Problem, indem sie dir ermöglichen, Funktionen und Typen zu schreiben, die mit mehreren Typen funktionieren und gleichzeitig vollständige Typsicherheit gewährleisten.
Stell dir vor, du möchtest den kleinsten Wert in einem slice finden. Ohne generics bräuchtest du für jeden Typ separate Funktionen:
func MinInt(values []int) int {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}
func MinFloat64(values []float64) float64 {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}Mit Generics schreibst du die Logik einmal und verwendest dabei einen Typparameter in eckigen Klammern:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}Das [T int | float64] deklariert einen Typparameter T, der entweder int oder float64 sein kann. Jetzt kannst du mit derselben Funktion Min([]int{3, 1, 2}) oder Min([]float64{3.5, 1.2}) aufrufen. Der Compiler prüft die Typen zur Kompilierzeit, sodass du Sicherheit ohne Laufzeitaufwand erhältst.
In den kommenden Lektionen werden wir Typparameter, Einschränkungen und generische Strukturen ausführlich untersuchen.
Aufgabe
EinfachErstellen wir einen Punkteanalysator, der mithilfe von Generics sowohl mit ganzzahligen als auch mit Gleitkommawerten arbeitet! Du erstellst eine einzige generische Funktion, die den Maximalwert in einem Slice finden kann, sodass kein doppelter Code für verschiedene numerische Typen erforderlich ist.
Du organisierst deinen Code auf zwei Dateien verteilt:
analyzer.go: Definiere deine generische Analysefunktion.Erstelle eine generische Funktion
Max[T int | float64](values []T) T, die den Maximalwert in einem Slice findet und zurückgibt. Die Funktion soll sowohl mit den Typenintals auchfloat64über den TypparameterTfunktionieren.Erstelle außerdem eine generische Funktion
Sum[T int | float64](values []T) T, die die Summe aller Werte in einem Slice berechnet und zurückgibt.main.go: Lies die Eingabe ein und demonstriere deine generischen Funktionen mit verschiedenen Typen.Lies eine Typanzeige (
intoderfloat) ein, anschließend eine Anzahl, gefolgt von ebenso vielen Werten. Wandle die Werte entsprechend dem Typ um, rufeMaxundSummit dem passenden Typ auf und gib die Ergebnisse aus.Gib bei ganzzahliger Eingabe Folgendes aus:
Max (int): [value] Sum (int): [value]Gib bei Gleitkommaeingabe mit einer Nachkommastelle aus:
Max (float): [value] Sum (float): [value]
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Typanzeige (
intoderfloat) - Zeile 2: Anzahl der Werte (Ganzzahl)
- Folgende Zeilen: Ein Wert pro Zeile
Beispiel für folgende Eingabe:
int
4
15
8
23
11Deine Ausgabe sollte folgendermaßen aussehen:
Max (int): 23
Sum (int): 57Beispiel für folgende Eingabe:
float
3
4.5
9.2
6.8Deine Ausgabe sollte folgendermaßen aussehen:
Max (float): 9.2
Sum (float): 20.5Beachte, wie dieselben generischen Funktionen sowohl ganzzahlige als auch Gleitkommadaten verarbeiten: Der Typparameter [T int | float64] ermöglicht es dem Compiler, für jede Verwendung typsicheren Code zu generieren, während du die Logik nur einmal schreiben musst.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Typindikator lesen
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// Anzahl lesen
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// Integer-Werte lesen
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: Max und Sum mit Integer-Slice aufrufen
// TODO: Ergebnisse im Format "Max (int): [value]" und "Sum (int): [value]" ausgeben
} else if typeStr == "float" {
// Float-Werte lesen
values := make([]float64, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
}
// TODO: Max und Sum mit float64-Slice aufrufen
// TODO: Ergebnisse mit einer Dezimalstelle im Format "Max (float): [value]" und "Sum (float): [value]" ausgeben
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler