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 wiederverwendbaren Codes für verschiedene Typen entweder, Funktionen zu duplizieren oder die leere Schnittstelle any mit Typzusicherungen zu verwenden. Generics lösen dieses Problem, indem sie es dir ermöglichen, Funktionen und Typen zu schreiben, die mit mehreren Typen arbeiten und dabei die vollständige Typsicherheit gewährleisten.
Stell dir vor, du möchtest den Minimalwert in einem Slice finden. Ohne Generics bräuchtest du für jeden Typ eine eigene Funktion:
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
}Die Deklaration [T int | float64] legt einen Typparameter T fest, 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 Typsicherheit ohne Laufzeit-Overhead erhältst.
In den kommenden Lektionen beschäftigen wir uns ausführlich mit Typparametern, Einschränkungen und generischen Structs.
Aufgabe
EinfachLass uns einen Score-Analysator erstellen, der mithilfe von Generics sowohl mit ganzzahligen als auch mit Gleitkomma-Scores 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 verteilst deinen Code auf zwei Dateien:
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 sollte mithilfe des TypparametersTsowohl mit den Typenintals auchfloat64funktionieren.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 führe deine generischen Funktionen mit verschiedenen Typen vor.Lies einen Typindikator (
intoderfloat) und anschließend eine Anzahl, gefolgt von entsprechend vielen Werten. Parse die Werte entsprechend dem Typ, rufeMaxundSummit dem passenden Typ auf und gib die Ergebnisse aus.Gib bei einer ganzzahligen Eingabe Folgendes aus:
Max (int): [value] Sum (int): [value]Gib bei einer Gleitkommaeingabe die Werte mit einer Nachkommastelle aus:
Max (float): [value] Sum (float): [value]
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Typindikator (
intoderfloat) - Zeile 2: Anzahl der Werte (Ganzzahl)
- Folgende Zeilen: Ein Wert pro Zeile
Zum Beispiel bei folgender Eingabe:
int
4
15
8
23
11sollte deine Ausgabe wie folgt aussehen:
Max (int): 23
Sum (int): 57Und bei folgender Eingabe:
float
3
4.5
9.2
6.8sollte deine Ausgabe wie folgt 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 erzeugen, während du die Logik nur einmal schreibst.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Typkennzeichen einlesen
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// Anzahl einlesen
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// Ganzzahlwerte einlesen
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 einem Ganzzahl-Slice aufrufen
// TODO: Ergebnisse im Format "Max (int): [value]" und "Sum (int): [value]" ausgeben
} else if typeStr == "float" {
// Gleitkommawerte einlesen
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 einem float64-Slice aufrufen
// TODO: Ergebnisse mit einer Nachkommastelle 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