List<? super Animal>
(lower bound) can addAnimal
and its subtypes.
List<? extends Animal>
(upper bound) cannot addAnimal
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 toObject
. Casting is required for this item to be used asAnimal
.
List<? extends Animal>
: Items retrieved are known to be at leastAnimal
, so no casting is needed to treat them asAnimal
.
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 aList<Animal>
,List<Object>
, etc., because they can all hold anAnimal
.getAnimal
can work withList<Animal>
,List<Dog>
, etc., safely returningAnimal
or any subtype without risking aClassCastException
.
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., <T extends Animal & Walker>
). 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.