Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?
Object-Oriented Design practice on Codemia
Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.
In Java, generics add a way to specify, at compile-time, the types of objects that collections or other compound types can contain. This feature helps programmers avoid errors by catching type mismatches during compilation, rather than at runtime. But, despite this vastly useful system, Java’s approach to generics has some nuances that might not be immediately clear, especially concerning polymorphism.
Understanding Java Generics and Polymorphism
In Java, polymorphism generally allows a subclass instance to be treated as an instance of a superclass. That is, it allows for generalization; a Dog as a specific kind of Animal can be treated just as an Animal. However, when it comes to generic types like List<Dog> and List<Animal>, they do not exhibit the same polymorphic behavior as you might expect from non-generic object hierarchies.
Consider the below example to understand this discrepancy:
Animalis a superclass ofDog.List<Dog>andList<Animal>are intuitively thought to hold a relationship whereList<Dog>could be a subclass ofList<Animal>. However, in Java, this is not the case.
Why List<Dog> is not a subclass of List<Animal>
Java implements generics through a mechanism known as type erasure. This means that the generic type information is used during compilation for type checking but is removed (erased) in the compiled bytecode. Here, List<Dog> and List<Animal> both become simply List at runtime.
Furthermore, if Java allowed List<Dog> to be treated as a List<Animal>, it would potentially lead to runtime errors, violating type safety. For example:
In the above code, if Java permitted List<Dog> to be a subtype of List<Animal>, adding an instance of Cat to animals would be valid at compile time. However, at runtime, it would lead to a ClassCastException when trying to treat a Cat as a Dog.
Generic Wildcards and Bounded Types
Java offers a way to work around the strict nature of generics with wildcards and bounded types, enhancing flexibility while maintaining type safety:
- Unbounded wildcard (
?) allows reading from a generic structure without knowing its exact type:
- Upper bounded wildcards (
? extends) allow the structure to be read as any type that is a subclass of the specified upper bound:
- Lower bounded wildcards (
? super) enable a structure to hold values of any type that is a superclass of the bounded type:
Practical Implications
In practice, using Java generics requires understanding where exact type matches are necessary and where some flexibility is required. Iterating through a List<? extends Animal> allows access to elements as Animal, but prevents adding unknown types. Conversely, List<? super Dog> accepts Dog or its subclasses being added but does not allow assumptions about reading the exact type.
| Feature | Description |
| Type Erasure | Generic type information is stripped out during compilation, turning all generic types into their raw types. |
| Invariance | Generic types are invariant, meaning List<Dog> is not a subtype nor a supertype of List<Animal>.
This prevents runtime type errors (e.g., adding a Cat to a List<Dog>). |
| Wildcards | Offer flexibility. ? extends allows reading as specific types, whereas ? super allows adding specific types. |
Conclusion
While generics do not support polymorphism as directly as class hierarchies do, through understanding and using wildcards appropriately, Java generics provide robust tools for managing collections of objects in a type-safe manner. This approach ultimately allows for flexibly manipulating these collections while preserving compile-time type safety and minimizing runtime errors. This systemic balance between flexibility and type safety is crucial in large-scale software development where both robustness and efficiency are paramount.
Related reading
- Is Meyers' implementation of the Singleton pattern thread safe?
- Is MVVM pointless?
- Is there a concept of inheritance for Kubernetes deployments?
- Is there a javadoc tag for documenting generic type parameters?
- Is Logback also affected by the Log4j zero-day vulnerability issue in Spring Boot?
- Is main a valid Java identifier?
- Is there a serializable generic Key/Value pair class in .NET?
- Is there a simple, elegant way to define singletons?

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Object-Oriented Design practice on Codemia
Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.