Variance : covariance et contravariance
Maîtrisez la covariance et la contravariance afin de gérer correctement les relations de sous-typage dans les types génériques.
Variance : covariance et contravariance est une leçon Scala for Backend Engineering & Functional Programming gratuite sur CoddyKit. Ceci est la leçon 2 sur 3. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Scala for Backend Engineering & Functional Programming, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Scala for Backend Engineering & Functional Programming comprend 3 leçons au total.
Qu'est-ce que la variance des types ?
En Scala, lorsque vous disposez d'une hiérarchie de types (par exemple, Cat est un sous-type de Animal), comment les types génériques se comportent-ils ?
Une List[Cat] est-elle considérée comme un sous-type de List[Animal] ? Pas toujours par défaut !
La variance des types permet de définir comment les relations de sous-typage sont conservées (ou inversées) pour les types génériques. Elle est essentielle pour écrire du code flexible et sûr du point de vue des types.
Covariance : types « producteurs » (+T)
La covariance est indiquée en plaçant un + devant le paramètre de type (par exemple, trait Box[+T]).
- Si
Aest un sous-type deB, alorsBox[A]devient un sous-type deBox[B]. - Considérez les types covariants comme des « producteurs » de
T. Ils ne peuvent apparaître que dans des positions de sortie (comme les types de retour des méthodes). - Ainsi, si vous attendez une
Box[Animal], vous pouvez fournir uneBox[Cat], car elle « produit » quelque chose de plus spécifique (unCatest unAnimal).
La covariance en pratique
Essayez d'exécuter cet exemple de trait Producer covariant :
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}")
}
}Quand utiliser la covariance
La covariance est sûre lorsque votre type générique ne « produit » que des valeurs de type T ou n'accepte jamais T comme argument.
- Les collections immuables comme
List[+T]en sont un excellent exemple. Vous pouvez considérer uneList[Cat]comme uneList[Animal], car vous ne faites qu'en « lire » desAnimal(ou leurs sous-types). - Vous ne pouvez pas ajouter un
Animalquelconque à uneList[Cat]lorsqu'elle est typée commeList[Animal], ce qui préserve la sûreté des types.
Contravariance : types « consommateurs » (-T)
La contravariance est indiquée en plaçant un - devant le paramètre de type (par exemple, trait Consumer[-T]).
- Si
Aest un sous-type deB, alorsConsumer[B]devient un sous-type deConsumer[A]. - Considérez les types contravariants comme des « consommateurs » de
T. Ils ne peuvent apparaître que dans des positions d'entrée (comme les types des paramètres des méthodes). - Ainsi, si vous attendez un
Consumer[Cat], vous pouvez fournir unConsumer[Animal], car il « consomme » quelque chose de plus général (il peut gérer n'importe quelAnimal, y compris unCat).
La contravariance en pratique
Essayez d'exécuter cet exemple de trait Consumer contravariant :
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.")
}
}Quand utiliser la contravariance
La contravariance est sûre lorsque votre type générique ne fait que « consommer » des valeurs de type T ou ne renvoie jamais T.
- Les fonctions, et plus précisément le type de leur paramètre d'entrée, en sont un exemple courant. Si une fonction peut traiter n'importe quel
Animal(Animal => Unit), elle peut certainement traiter unCat. Ainsi,(Animal => Unit)est un sous-type de(Cat => Unit). - Cela offre davantage de flexibilité lorsque vous transmettez des fonctions comme arguments.
Invariance : le comportement par défaut
Si vous ne spécifiez ni + ni -, le paramètre de type est invariant. Il s'agit du comportement par défaut en Scala.
Box[A]est un sous-type deBox[B]uniquement siAetBsont exactement du même type.- Cela est souvent nécessaire pour les collections modifiables (par exemple,
Array[T]) afin d'éviter les problèmes de sûreté des types, car vous pourriez à la fois lire et écrire différents sous-types.
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.")
}
}Fonctions : covariance et contravariance
Les types de fonctions de Scala, Function1[-A, +B], combinent élégamment les deux formes de variance :
- Le paramètre d'entrée
Aest contravariant (-A). Cela signifie qu'une fonction qui accepte un type plus général (par exemple,Animal) peut être utilisée là où l'on attend une fonction acceptant un type plus spécifique (par exemple,Cat). - Le type de retour
Best covariant (+B). Cela signifie qu'une fonction qui renvoie un type plus spécifique (par exemple,Cat) peut être utilisée là où l'on attend une fonction renvoyant un type plus général (par exemple,Animal).
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}")
}
}Vérification rapide : règles de variance
Considérez le trait suivant :
trait Handler[T] {
def handle(item: T): Unit
}Pour permettre l'utilisation de Handler[Animal] là où un Handler[Cat] est attendu (sachant que Cat étend Animal), quelle annotation de variance T doit-il avoir ?
Récapitulatif : maîtriser la variance
Vous maîtrisez désormais la variance en Scala ! Voici un bref récapitulatif :
- Covariance (
+T) : permet àContainer[Subtype]d'être un sous-type deContainer[Supertype]. Utile pour les types « producteurs » qui renvoient uniquementT. - Contravariance (
-T) : permet àContainer[Supertype]d'être un sous-type deContainer[Subtype]. Utile pour les types « consommateurs » qui acceptent uniquementTen entrée. - Invariance : comportement par défaut. Les types doivent correspondre exactement.
Comprendre la variance vous aide à créer du code générique Scala plus flexible et plus sûr du point de vue des types !
Questions Fréquemment Posées
La leçon « Variance : covariance et contravariance » est-elle gratuite ?
Oui — le texte complet de « Variance : covariance et contravariance » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Scala for Backend Engineering & Functional Programming, passe à CoddyKit PRO. Le cours Scala for Backend Engineering & Functional Programming comprend 3 leçons au total.
Qu'est-ce que j'apprendrai dans « Variance : covariance et contravariance » ?
Maîtrisez la covariance et la contravariance afin de gérer correctement les relations de sous-typage dans les types génériques. Tu pratiques Scala for Backend Engineering & Functional Programming avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Scala for Backend Engineering & Functional Programming ?
Aucune expérience préalable n'est requise. Scala for Backend Engineering & Functional Programming sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 3.
Combien de temps prend la leçon « Variance : covariance et contravariance » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Scala for Backend Engineering & Functional Programming ?
Oui. Chaque leçon Scala for Backend Engineering & Functional Programming inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Génériques et paramètres de type
- Variance : covariance et contravariance
- Classes de types et implicites