Varianz: Kovarianz und Kontravarianz
Beherrschen Sie Kovarianz und Kontravarianz, um Subtyping-Beziehungen in generischen Typen korrekt zu handhaben.
Varianz: Kovarianz und Kontravarianz ist eine kostenlose Scala for Backend Engineering & Functional Programming-Lektion auf CoddyKit. Dies ist Lektion 2 von 3. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Scala for Backend Engineering & Functional Programming-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Scala for Backend Engineering & Functional Programming-Kurs umfasst insgesamt 3 Lektionen.
Was ist Typvarianz?
Was geschieht in Scala mit generischen Typen, wenn eine Typhierarchie vorhanden ist (z. B. ist Cat ein Subtyp von Animal)?
Ist ein List[Cat] ein Subtyp von List[Animal]? Standardmäßig nicht immer!
Typvarianz ermöglicht es, festzulegen, wie Subtypbeziehungen für generische Typen erhalten bleiben (oder umgekehrt werden). Das ist entscheidend für flexiblen und typsicheren Code.
Kovarianz: „Produzent“-Typen (+T)
Kovarianz wird durch ein + vor dem Typparameter angegeben (z. B. trait Box[+T]).
- Wenn
Aein Subtyp vonBist, wirdBox[A]zu einem Subtyp vonBox[B]. - Betrachten Sie kovariante Typen als „Produzenten“ von
T. Sie dürfen nur an Ausgabepositionen vorkommen, etwa als Rückgabetypen von Methoden. - Wenn ein
Box[Animal]erwartet wird, kann daher einBox[Cat]übergeben werden, weil es etwas Spezifischeres „produziert“ (einCatist einAnimal).
Kovarianz in der Praxis
Führen Sie dieses Beispiel für ein kovariantes Producer-Trait aus:
class Animal
class Cat extends Animal
trait Producer[+T] {
def produce: T // Output position
}
class CatProducer extends Producer[Cat] {
def produce: Cat = new Cat
}
object Main {
def main(args: Array[String]): Unit = {
val catProducer: Producer[Cat] = new CatProducer
// Because Producer is covariant, Producer[Cat] is a subtype of Producer[Animal]
val animalProducer: Producer[Animal] = catProducer
println("Assigned CatProducer to AnimalProducer.")
println(s"Produced: ${animalProducer.produce.getClass.getSimpleName}")
}
}Wann Sie Kovarianz verwenden sollten
Kovarianz ist sicher, wenn Ihr generischer Typ nur Werte des Typs T „produziert“ oder T niemals als Argument entgegennimmt.
- Unveränderliche Collections wie
List[+T]sind ein typisches Beispiel. Sie können eineList[Cat]alsList[Animal]behandeln, weil Sie daraus nurAnimal-Werte (oder deren Subtypen) „lesen“. - Sie können kein beliebiges
Animalzu einerList[Cat]hinzufügen, wenn diese alsList[Animal]typisiert ist. So bleibt die Typsicherheit erhalten.
Kontravarianz: „Konsument“-Typen (-T)
Kontravarianz wird durch ein - vor dem Typparameter angegeben (z. B. trait Consumer[-T]).
- Wenn
Aein Subtyp vonBist, wirdConsumer[B]zu einem Subtyp vonConsumer[A]. - Betrachten Sie kontravariante Typen als „Konsumenten“ von
T. Sie dürfen nur an Eingabepositionen vorkommen, etwa als Parametertypen von Methoden. - Wenn ein
Consumer[Cat]erwartet wird, kann daher einConsumer[Animal]übergeben werden, weil es etwas Allgemeineres „konsumiert“ (es kann jedesAnimalverarbeiten, einschließlich einesCat).
Kontravarianz in der Praxis
Führen Sie dieses Beispiel für ein kontravariantes Consumer-Trait aus:
class Animal
class Cat extends Animal
trait Consumer[-T] {
def consume(item: T): Unit // Input position
}
class AnimalConsumer extends Consumer[Animal] {
def consume(item: Animal): Unit = {
println(s"Consumed an animal: ${item.getClass.getSimpleName}")
}
}
object Main {
def main(args: Array[String]): Unit = {
val animalConsumer: Consumer[Animal] = new AnimalConsumer
// Because Consumer is contravariant, Consumer[Animal] is a subtype of Consumer[Cat]
val catConsumer: Consumer[Cat] = animalConsumer
catConsumer.consume(new Cat)
println("Assigned AnimalConsumer to CatConsumer.")
}
}Wann Sie Kontravarianz verwenden sollten
Kontravarianz ist sicher, wenn Ihr generischer Typ nur Werte des Typs T „konsumiert“ oder T niemals zurückgibt.
- Ein häufiges Beispiel sind Funktionen, insbesondere der Typ des Eingabeparameters. Wenn eine Funktion jedes
Animalverarbeiten kann (Animal => Unit), kann sie selbstverständlich auch einCatverarbeiten. Daher ist(Animal => Unit)ein Subtyp von(Cat => Unit). - Das ermöglicht größere Flexibilität, wenn Funktionen als Argumente übergeben werden.
Invarianz: Das Standardverhalten
Wenn Sie weder + noch - angeben, ist der Typparameter invariant. Das ist der Standard in Scala.
Box[A]ist nur dann ein Subtyp vonBox[B], wennAundBexakt denselben Typ haben.- Das ist bei veränderlichen Collections häufig notwendig (z. B. bei
Array[T]), um Probleme mit der Typsicherheit zu verhindern, da Sie verschiedene Subtypen sowohl lesen als auch schreiben können.
class Food
class Apple extends Food
// Invariant Box
class Box[T](val item: T) {
def getContent: T = item
}
object Main {
def main(args: Array[String]): Unit = {
val appleBox = new Box(new Apple)
// The following line would cause a compile error:
// val foodBox: Box[Food] = appleBox
println(s"An Apple Box contains: ${appleBox.getContent.getClass.getSimpleName}")
println("Box[Apple] is NOT a subtype of Box[Food] (invariant).")
println("The types must match exactly for invariant types.")
}
}Funktionen: Kovarianz und Kontravarianz
Scalas Funktionstypen Function1[-A, +B] verbinden beide Varianzarten auf elegante Weise:
- Der Eingabeparameter
Aist kontravariant (-A). Das bedeutet, dass eine Funktion, die einen allgemeineren Typ (z. B.Animal) akzeptiert, dort verwendet werden kann, wo eine Funktion erwartet wird, die einen spezifischeren Typ (z. B.Cat) akzeptiert. - Der Rückgabetyp
Bist kovariant (+B). Das bedeutet, dass eine Funktion, die einen spezifischeren Typ (z. B.Cat) zurückgibt, dort verwendet werden kann, wo eine Funktion erwartet wird, die einen allgemeineren Typ (z. B.Animal) zurückgibt.
class Vehicle
class Car extends Vehicle
object Main {
def main(args: Array[String]): Unit = {
// Contravariance for input: (Vehicle => Unit) is a subtype of (Car => Unit)
val printVehicle: Vehicle => Unit = (v: Vehicle) => println(s"Printing vehicle: ${v.getClass.getSimpleName}")
val printCar: Car => Unit = printVehicle // OK: A general printer can print a specific car
printCar(new Car)
// Covariance for output: (() => Car) is a subtype of (() => Vehicle)
val getCar: () => Car = () => new Car
val getVehicle: () => Vehicle = getCar // OK: A specific producer can fulfill a general request
println(s"Got vehicle: ${getVehicle().getClass.getSimpleName}")
}
}Schnelltest: Regeln zur Varianz
Betrachten Sie das folgende Trait:
trait Handler[T] {
def handle(item: T): Unit
}Welche Varianzannotation sollte T haben, damit Handler[Animal] dort verwendet werden kann, wo ein Handler[Cat] erwartet wird (wobei Cat Animal erweitert)?
Zusammenfassung: Varianz beherrschen
Sie beherrschen jetzt die Varianz in Scala! Hier eine kurze Zusammenfassung:
- Kovarianz (
+T): Ermöglicht, dassContainer[Subtype]ein Subtyp vonContainer[Supertype]ist. Nützlich für „Produzenten“-Typen, dieTnur zurückgeben. - Kontravarianz (
-T): Ermöglicht, dassContainer[Supertype]ein Subtyp vonContainer[Subtype]ist. Nützlich für „Konsumenten“-Typen, dieTnur als Eingabe akzeptieren. - Invarianz: Das Standardverhalten. Die Typen müssen exakt übereinstimmen.
Wenn Sie die Varianz verstehen, können Sie flexibleren und typsicheren generischen Code in Scala erstellen!
Häufig gestellte Fragen
Ist die Lektion „Varianz: Kovarianz und Kontravarianz“ kostenlos?
Ja — der vollständige Text von „Varianz: Kovarianz und Kontravarianz“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Scala for Backend Engineering & Functional Programming-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Scala for Backend Engineering & Functional Programming-Kurs umfasst insgesamt 3 Lektionen.
Was lerne ich in „Varianz: Kovarianz und Kontravarianz“?
Beherrschen Sie Kovarianz und Kontravarianz, um Subtyping-Beziehungen in generischen Typen korrekt zu handhaben. Du übst Scala for Backend Engineering & Functional Programming mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Scala for Backend Engineering & Functional Programming zu starten?
Keine Vorkenntnisse erforderlich. Scala for Backend Engineering & Functional Programming auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 3.
Wie lange dauert die Lektion „Varianz: Kovarianz und Kontravarianz“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Scala for Backend Engineering & Functional Programming-Lektion Code schreiben und ausführen?
Ja. Jede Scala for Backend Engineering & Functional Programming-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Generics und Typparameter
- Varianz: Kovarianz und Kontravarianz
- Typklassen und Implicits