A Guide to Generic Type Arguments in Java
Posted By Amit Kumar | 30-Nov-2018
Generics capabilities were added in Java way back in version J2SE 1.5, but in Java 8, suddenly the Javadocs are filled with method signatures like the following :
In this blog, we look to understand the above parameters being used
One of the simplest usages of Generic Type parameters we know is
List<String> strings = new ArrayList<String>();
//and since Java 7 we can also use
List<String> strings = new ArrayList<>();
Declaring the data type of the collection accomplishes two goals:
We can’t place the wrong type inside a collection and an explicit cast is not required to retrieve the values. Example - 1
Some of these methods use the declared generic type, E, as either an argument or a return type. Some (specifically, clear and contains) don’t use the type at all. Others involve some kind of a wildcard, using a question mark.
One of the most common mistakes a person can make in using the above functions is - getting confused between the relationship in List<tring> and List<Object>. Consider the add method in the List<E> interface -
The problem is that List<String> is not a subclass of List<Object>. The only instances we can add to it are of the type String and hence we say that the parameterized type is invariant.
If we could assign a List<String> to a List<Object> we could add something that wasn’t string to the list, causing a cast exception when you try to retrieve it using the List<String> reference.
For similar situations do not arise we can use wildcards instead of invariant parameter types
A wildcard is a type of argument that uses a question mark, ? with an optional upper or lower bound.
Upper Bounded Wildcards
An upper bounded wildcard defines the
Lower Bounded Wildcards
A lower bounded wildcard means any parent of the class is acceptable. We use super keyword to specify a lower bound. The implication, in the case of a List<? super Number>
Consider a method called numsUpTo that takes two arguments, an integer, and a list to populate with all the numbers up to the first argument, as in Example.
One final note before looking at examples from the Java 8 API. A type parameter can have multiple bounds. The bounds are separated by an ampersand when they are defined:
T extends Runnable & AutoCloseable
You can have as many interface bounds as you like, but only one can be a class. If you have a class as a bound, it must be first in the list.
Examples from the Java 8 API
With all that in mind, now it’s time to review some examples from the Java 8 docs.
In the java.util.stream.Stream interface, consider the max method:
Optional<T> max(Comparator<? super T> comparator)
Note the use of the lower bounded wildcard in the Comparator. The max method returns the maximum element of a stream by applying the supplied Comparator to it. The return type is Optional<T>, because there may not be a return value if the stream is empty. The method wraps the maximum in an Optional if there is one, and returns an empty Optional if not.