Variance in Java


Java, with its robust object-oriented programming features, offers a multitude of mechanisms for programmers to develop flexible and efficient code. One such concept, often overlooked but critically important, is variance. Understanding variance is crucial for mastering Java, especially when working with generics and collections. This article provides an in-depth exploration of variance in Java, covering its types - covariance, contravariance, and invariance - and their practical applications.

Understanding Variance

Variance refers to how subtyping between more complex types relates to subtyping between their components. In simpler terms, it determines how the type hierarchy of classes is preserved when these classes are used as type parameters. Variance becomes particularly relevant when dealing with generics, providing a framework to ensure type safety while allowing some degree of flexibility in assignments.

Variance can be divided into three main types:

  • Covariance − If ClassB is a subclass of ClassA, then Collection can be treated as a subclass of Collection.

  • Contravariance − If ClassB is a subclass of ClassA, then Collection can be treated as a subclass of Collection.

  • Invariance − Collection and Collection have no subtype relationship, regardless of the relationship between ClassA and ClassB.

Let's delve deeper into each of these concepts.

Covariance in Java

Covariance is achieved in Java through the use of the wildcard with an extends clause. Let's consider an example −

List<Animal> animals = new ArrayList<>();
<List<super Cat>cats=animals;

In this scenario, you can add a Cat object or any of its instances to cats, but you cannot read from cats and treat the result as a Cat, because it might hold any super type of Cat, including Animal or Object. Therefore, you can write into cats, but you cannot read from it in a type-safe manner

Invariance in Java

Invariance is the default behavior in Java and means there is no subtype relationship between Collection and Collection, regardless of the relationship between ClassA and ClassB. This might seem restrictive, but it's essential for type safety. In Java, List is not a subtype of List, even though String is a subtype of Object. This is because Java collections are mutable and allowing such a relationship would lead to runtime type errors.

List<String> strings = new ArrayList<>();
// Compile error: Incompatible types
List<Object> objects = strings;

In the above example, even though String is a subtype of Object, List is not a subtype of List, hence the compilation error.

This feature might initially appear as a limitation, but it's a vital aspect of Java's type system to ensure that no unsafe operations are performed. If List were a subtype of List, you could add an Object that is not a String into a List, leading to a ClassCastException at runtime.

List<String> strings = new ArrayList<>();
// If this were allowed...
List<Object> objects = strings;
// ...this would put a non-String into a List<String>
objects.add(new Object());
String str= strings.get(0); // ClassCastException

This example illustrates why it's essential to maintain invariance for type safety.

Bounded Type Parameters and Variance

Covariance and contravariance are most commonly used with bounded type parameters. Bounded type parameters are a way of indicating that a type parameter must be a subtype (extends keyword) or a supertype (super keyword) of a certain type. This allows for flexibility in what types can be passed to a method while still maintaining type safety.

For example, you might have a method that operates on lists of Number and all its subclasses −

public <T extends Number> void processNumbers(List<T> numbers) { /* ... */ }

In this method, T is a bounded type parameter that must be a Number or a subtype of Number. This allows the method to operate on List, List, List, etc., demonstrating covariance

Conclusion

In conclusion, understanding variance in Java is critical for effectively working with generics and collections. It allows for flexible code while ensuring type safety.

Covariance, using the extends keyword, allows a subclass to stand in for a superclass, enabling more generic object handling. Contravariance, on the other hand, permits the superclass to substitute for a subclass, enabling the execution of broader actions on more specific objects.

Invariance preserves type safety by ensuring that collections of different types, even if related by inheritance, maintain their uniqueness and prevent runtime type errors.

Updated on: 19-Jul-2023

114 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements