Forem

Anh Trần Tuấn
Anh Trần Tuấn

Posted on • Originally published at tuanh.net on

Understanding Covariance and Contravariance in Java

1. What is Covariance and Contravariance in Java?

Image

1.1 Covariance in Java

Covariance allows us to assign a more derived type to a less derived type. In simpler terms, it refers to the ability of a generic type to preserve the assignment compatibility when dealing with a subtype. This concept is commonly found in arrays and generic types in Java.

Example:

class Animal { 
    void sound() { System.out.println("Animal sound"); } 
}
class Dog extends Animal { 
    void sound() { System.out.println("Dog barks"); } 
}

public class CovariantExample {
    public static void main(String[] args) {
        Animal[] animals = new Dog[10]; // Covariance in arrays
        animals[0] = new Dog(); // Works because Dog is a subtype of Animal

        for (Animal animal : animals) {
            if (animal != null) {
                animal.sound();
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this case, a Dog[] can be assigned to an Animal[], showcasing covariance in arrays.

1.2 Covariance with Generics (? extends)

With generics, covariance is applied using the ? extends T wildcard. This means that we can read from the generic structure, but we cannot modify it (other than assigning null). This limitation ensures type safety.

Example:

import java.util.ArrayList;
import java.util.List;

public class CovarianceGenerics {
    public static void main(String[] args) {
        List<? extends Animal> animals = new ArrayList<Dog>();
        // Cannot add a Dog or an Animal because it is read-only
        // animals.add(new Dog()); // Compilation error
        // animals.add(new Animal()); // Compilation error

        for (Animal animal : animals) {
            animal.sound(); // This works fine because we are reading from the list
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The ? extends wildcard ensures that we can access elements safely, knowing they will be at least of type Animal, but it prevents us from modifying the list since the exact subtype is unknown.

1.3 When to Use Covariance

Covariance is useful when you need to process a collection of objects but don’t need to modify them. If you are only reading elements, covariance ensures that all elements are a subtype of a given class while preserving type safety.

1.4 Demo Result

In the covariance example with arrays, the program successfully outputs Dog barks for each element in the animals[] array, even though the array is declared as Animal[]. This proves the flexibility of covariance when dealing with inheritance hierarchies.

2. Understanding Contravariance in Java

2.1 Contravariance with Generics (? super)

Contravariance is the opposite of covariance. With contravariance, you can assign a more general type to a reference that expects a more specific type. In Java, contravariance is implemented using the ? super T wildcard.

This allows you to modify the collection by adding elements, but reading from the collection is restricted to only objects of type Object (or a safe downcast).

Example:

import java.util.ArrayList;
import java.util.List;

public class ContravarianceExample {
    public static void main(String[] args) {
        List<? super Dog> animals = new ArrayList<Animal>(); // Contravariant type
        animals.add(new Dog()); // Allowed because Dog is a subtype of Animal
        // animals.add(new Animal()); // Compilation error: Only Dog or its subclasses allowed

        Object obj = animals.get(0); // Can only retrieve as Object
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, we can add Dog objects to the list, but we cannot add Animal objects directly. Contravariance ensures that the collection will only contain elements of type Dog or a subtype, preserving type safety while allowing modifications.

2.2 When to Use Contravariance

Contravariance is helpful when you need to modify a collection of objects, specifically when adding elements to a data structure. It ensures that the elements added are compatible with a specific type, providing flexibility in situations where you expect different subtypes to be added.

3. Covariance vs Contravariance: Key Differences

3.1 Covariance (? extends)

Covariance is used when you want to read from a collection. The collection is treated as a source, meaning you only need to ensure that the elements are subtypes of a specific type. However, it restricts you from modifying the collection.

3.2 Contravariance (? super)

Contravariance is used when you want to write to a collection. It allows you to add elements while enforcing that they are compatible with a specific type or any of its subtypes. However, it restricts reading the collection to ensure type safety.

3.3 How to Decide Between Covariance and Contravariance

To decide between covariance and contravariance, ask yourself: Do you need to read, write, or both?

  • If you only need to read from the collection, use ? extends.
  • If you need to write to the collection, use ? super.
  • If you need both, consider using a more specific generic type or redesigning the structure.

4. Demo Results: What You Have Learned

By using ? extends, you allow flexibility in reading data from a collection while keeping the integrity of the types. The ? super wildcard allows flexibility when adding elements to a collection, ensuring that only the expected subtypes are added.

These two concepts, covariance and contravariance, are crucial when dealing with inheritance and generics in Java. Knowing when and how to apply them will enhance your ability to create robust, type-safe code.

5. Conclusion

Understanding covariance and contravariance in Java is essential for handling generics and wildcards effectively. These concepts offer flexibility in reading and writing data while ensuring type safety in inheritance hierarchies. By applying the techniques outlined in this article, you can write more adaptable and maintainable code.

If you have any questions or need further clarification on covariance and contravariance, feel free to leave a comment below!

Read posts more at : Understanding Covariance and Contravariance in Java

Top comments (0)