Generic Invariance in Java
Generic invariance means that, in Java, `Generic<Child>` is not generally a subtype of `Generic<Parent>`, even when `Child` extends `Parent`. This prevents type-unsafe writes through a reference using a broader type argument.
Java generics are invariant by default. If `Dog` extends `Animal`, the relationship between `Dog` and `Animal` does not automatically extend to `List<Dog>` and `List<Animal>`.
The key reason is that a mutable generic structure may both produce and consume values. If `List<Dog>` could be assigned to `List<Animal>`, code holding the `List<Animal>` reference could add a `Cat`, which would violate the original list's expectation that it contains only `Dog` values.
For example, `List<Dog> dogs = new ArrayList<>();` cannot be assigned directly to `List<Animal> animals`. To express a read-only producer relationship, Java provides a bounded wildcard such as `List<? extends Animal>`. To express a consumer relationship, `List<? super Dog>` can be used.
Invariance is different from wildcard variance. The generic class itself remains invariant, while wildcard types let a particular reference safely describe a family of parameterized types. This distinction is important when designing method parameters and APIs.
Key points
- `List<Dog>` is not a subtype of `List<Animal>` even when `Dog extends Animal`.
- Invariance prevents unsafe writes through a reference with a broader type argument.
- Use `? extends T` when a generic value is primarily a producer of `T` values.
- Use `? super T` when a generic value is intended to consume `T` values.
Interview questions
Why is `List<Dog>` not a subtype of `List<Animal>`?
Because Java generic types are invariant by default. If the assignment were allowed, code receiving a `List<Animal>` could add an `Animal` that is not a `Dog`. That would break the type guarantee of the original `List<Dog>`.
How would you accept a list of any `Animal` subtype when a method only reads from it?
Use `List<? extends Animal>` as the parameter type. The wildcard means the list contains some specific but unknown subtype of `Animal`. You can safely read elements as `Animal`, but you cannot add an arbitrary `Animal` because the actual subtype is unknown.
What is the difference between `List<Animal>` and `List<? extends Animal>`?
`List<Animal>` means the list is specifically parameterized with `Animal`, so an `Animal` can be added to it. `List<? extends Animal>` can refer to a `List<Animal>`, `List<Dog>`, or another subtype-specific list. Because the actual type argument is unknown, arbitrary `Animal` values cannot safely be added through that reference.
Explain the PECS rule in relation to generic invariance.
PECS stands for Producer Extends, Consumer Super. If a parameterized object primarily produces values for the caller, an upper-bounded wildcard such as `? extends T` usually provides the needed flexibility. If it consumes values of type `T`, a lower-bounded wildcard such as `? super T` is generally appropriate. This works around the limitations of invariant generic types while preserving type safety.
Why are Java arrays and generics different with respect to variance?
Java arrays are covariant, so an array of a subtype can be assigned to an array of its supertype. Because arrays retain their component type at runtime, an invalid store can be detected with `ArrayStoreException`. Generic type arguments are erased from ordinary runtime representation, so Java instead enforces generic invariance at compile time and uses wildcards for controlled variance.