Explain Codes LogoExplain Codes Logo

Covariance, Invariance, and Contravariance explained in plain English?

java
type-safety
generics
polymorphism
Alex KataevbyAlex Kataev·Nov 24, 2024
TLDR
  • Covariance: Assign a subclass type to a superclass reference. A feature of java arrays because they are covariant:

    Object[] objects = new String[10]; // As far as Java cares, String is a stairway to Object[]
  • Invariance: Types must match exactly: no subclass or superclass assignments are allowed. Java's generics are like a stubborn child, they are invariant.

    List<String> strings = new ArrayList<>(); // List<Object> objects = strings; // Compile error: Type mismatch, List goes "Nope, not today!"
  • Contravariance: Enabled in Java via wildcards (<?>). It helps the collection to hold a superclass of the stated type:

    List<? super String> list = new ArrayList<Object>(); // Object - Generous big-hearted superclass of String!

Simply put, covariance = more specific is OK, invariance = must be exact, contravariance = more generic is OK (using wildcards but not any wizard spells).

Covariance in method overriding:

From Java's 1.5 version onwards, covariance in method return types was possible when overriding. A method in superclass returning T type can be overridden in subclass to return its subclass type too, thereby promoting code reusability and fluidity. It's like the baton pass in a relay race - smooth transition!

Generics and Invariance: A disciplined duo

Java Generics are designed to ensure type safety and abide by invariance. So, when dealing with collections, it's like the strict librarian preventing you from placing a horror book in the children's section - ensuring you don't add an incompatible object and create runtime errors.

Decoding Contravariance with generics:

Contravariance could feel less intuitive but it's a matter of perspective—it deals more with receiving types than returning them. Thanks to wildcards like <?>, Java achieves contravariance. A Consumer<? super T> can gladly accept T and any of its superclasses, just like an all-accepting, broad-minded philosopher.

Arrays and Variance: An interesting interaction:

While the covariant nature of Java arrays has its charm, it can lead to potential perils:

Object[] objectArray = new String[1]; objectArray[0] = 1; // Sneaky runtime error rather than compile-time error, like a hidden Netflix spoiler. Not cool, right?

Using generic collections and maintaining invariance dodges this bullet efficiently.

Clearing the air: Addressing common misconceptions

  • Covariance doesn't mean you can put Pear in Apple basket. Despite both being fruits, List<Pear> is not a subtype of List<Apple>.
  • Contravariance with wildcards <? super T> does not allow you to get T type out of the collection without casting. After all, even loosen rules have fine prints.

Practical application in coffee, oh I mean code

  • Covariance can come in handy when dealing with read-only data structures where a wider range of types being returnable is a good trait.
  • Contravariance is like a secret ingredient, especially useful for functional interfaces, such as Comparator<T>, which lets a comparator for a type to be used with any subtypes.

Superhero crossover: Polymorphism and Type Variance

Superpowers of the Java's polymorphicsm and type variace are revealed when:

  • Covariant return types strengthen the Liskov substitution principle, enabling more specialised types to be returned by methods in subclasses.
  • Remind yourself that Invariance in generics doesn't allow a List<Dog> for a List<Animal>, even though Dog is a good boi, I mean subtype of Animal.