0Pricing
Scala for Backend Engineering & Functional Programming · Lektion

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 A ein Subtyp von B ist, wird Box[A] zu einem Subtyp von Box[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 ein Box[Cat] übergeben werden, weil es etwas Spezifischeres „produziert“ (ein Cat ist ein Animal).

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 eine List[Cat] als List[Animal] behandeln, weil Sie daraus nur Animal-Werte (oder deren Subtypen) „lesen“.
  • Sie können kein beliebiges Animal zu einer List[Cat] hinzufügen, wenn diese als List[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 A ein Subtyp von B ist, wird Consumer[B] zu einem Subtyp von Consumer[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 ein Consumer[Animal] übergeben werden, weil es etwas Allgemeineres „konsumiert“ (es kann jedes Animal verarbeiten, einschließlich eines Cat).

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 Animal verarbeiten kann (Animal => Unit), kann sie selbstverständlich auch ein Cat verarbeiten. 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 von Box[B], wenn A und B exakt 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 A ist 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 B ist 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, dass Container[Subtype] ein Subtyp von Container[Supertype] ist. Nützlich für „Produzenten“-Typen, die T nur zurückgeben.
  • Kontravarianz (-T): Ermöglicht, dass Container[Supertype] ein Subtyp von Container[Subtype] ist. Nützlich für „Konsumenten“-Typen, die T nur 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

  1. Generics und Typparameter
  2. Varianz: Kovarianz und Kontravarianz
  3. Typklassen und Implicits
← Zurück zu Scala for Backend Engineering & Functional Programming