List super Animal> (lower bound) can add Animal and its subtypes.
List extends Animal> (upper bound) cannot add Animal or any subtype (except null).
Reading bounded lists
When reading lower- and upper-bound lists, remember this:
List super Animal>: Items retrieved from a lower-bound list are of an indeterminate type up to Object. Casting is required for this item to be used as Animal.
List extends Animal>: Items retrieved are known to be at least Animal, so no casting is needed to treat them as Animal.
An example of upper- and lower-bound lists
Imagine you have a method to add an Animal to a list and another method to process animals from a list:
void addAnimal(List super Animal> animals, Animal animal) {
animals.add(animal); // This is valid.
}
Animal getAnimal(List extends Animal> animals, int index) {
return animals.get(index); // No casting needed, returns Animal type.
}
In this setup:
addAnimal can accept a List, List, etc., because they can all hold an Animal.
getAnimal can work with List, List, etc., safely returning Animal or any subtype without risking a ClassCastException.
This shows how Java generics use the extends and super keywords to control what operations are safe regarding reading and writing, aligning with the intended operations of your code.
Conclusion
Knowing how to apply advanced concepts of generics will help you create robust components and Java APIs. Let’s recap the most important points of this article.
Bounded type parameters
You learned that bounded type parameters limit the allowable types in generics to specific subclasses or interfaces, enhancing type safety and functionality.
Wildcards
Use wildcards (? extends and ? super) to allow generic methods to handle parameters of varying types, adding flexibility while managing covariance and contravariance. In generics, wildcards enable methods to work with collections of unknown types. This feature is crucial for handling variance in method parameters.
Type erasure
This advanced feature enables backward compatibility by removing generic type information at runtime, which leads to generic details not being maintained post-compilation.
Generic methods and type inference
Type inference reduces verbosity in your code, allowing the compiler to deduce types from context and simplify code, especially from Java 7 onwards.
Multiple bounds in Java generics
Use multiple bounds to enforce multiple type conditions (e.g., ). Ensuring parameters meet all the specified requirements promotes functional and type safety.
Lower bounds
These support write operations by allowing additions of (in our example) Animal and its subtypes. Retrieves items recognized as Object, requiring casting for specific uses due to the general nature of lower bounds.
Upper bounds
These facilitate read operations, ensuring all retrieved items are at least (in our example) Animal, eliminating the need for casting. Restricts additions (except for null) to maintain type integrity, highlighting the restrictive nature of upper bounds.