The Get & the Put Principle
This lesson provides examples on the get and the put principle to be followed when working with generics.
Question # 1
What is the Get and Put principle?
We use the ?
wildcard to make methods more flexible and accept a larger number of types. For instance, consider the method below:
void printNumbers(List<Number> nums) {
for (Number number : nums)
System.out.println(number);
}
The above method will not accept a list of Integer
or a list of Double
etc. However, we can change the parametrized type to a bounded wildcard which will allow us to pass a list of all the subtypes of Number
.
void printNumbers(List<? extends Number> nums) {
for (Number number : nums)
System.out.println(number);
}
The parameter List<? extends Number>
tells us that the method printNumbers
accepts a list of type that extends Number
. The guarantee we have is that whatever the type is contained within the list we can read the value into a reference variable of type Number. Note we can't determine, if it's an integer or a double, just that it is of type number. Thus we can safely extract out elements of type T from a structure when the parametrized type is ? extends T
. The Get Principle is to use an extends wildcard when you want to only get values out of a structure.
Note that the following snippet won't compile, even though intuitively it may seem that it should. We are trying to insert an integer into a list of unknown types that extend number and since integer also extends number we should be able to add the integer.
Adding integer into a list of unknown type that extends number
List<? extends Number> listOfNumbers = new ArrayList<>();
Integer i = Integer.valueOf(5);
// Attempting to put an integer in a list of uknown types that extend Number
listOfNumbers.add(i); // <--- compile time error
This may seem confusing, since we can successfully add an integer into a list of numbers.
Adding integer into a list of numbers
List<Number> list = new ArrayList<>();
Integer i = Integer.valueOf(5);
// Attempting to put an integer in a list of Number
list.add(i); // <--- compiles
The reason, we can't insert an integer into a list of type ? extends T
is because we could end up storing, for example a double into a list of integers as shown below
List<Integer> listOfInts = new ArrayList<>();
List<? extends Number> reference = listOfInts; // Allowed
reference.add(new Double(4.0)); // <--- compile time error
The above snippet exemplifies why adding anything that extends a Number can't be added to a list that extends Number. In summary, think of ? extends T as containing every type in an interval bounded by the type of null below and by T above.
Question # 2
Consider the code snippet below. This time we are saving a Number into a list of types that extends Number. Will this work?
void addNumber(Number num) {
List<? extends Number> listOfNumbers = new ArrayList<>();
listOfNumbers.add(num); // Will this work?
}
Compiles
Doesn’t compile
Compiles with a checked warning and throws a runtime ClassCastException
The above code will never compile because we are trying to add a number into a list of a type that extends Number and we can't guarantee the type of the number being passed into the method. Actually, the way the code is written, it is pretty useless. The list can never be added to since it is being declared inside the method and new-ed up at the same time.
Usually, list with unbounded wildcard or an extends wildcard bound are used to declare the parameter types in the method signature and not as variables inside a method.
Note that List<? extends Number>
does not mean list of objects of different types, all of which extend Number. Rather it implies list of objects of a single type which extends Number.
Question # 3
Can we consider List<?>
as an immutable list since we can never add to this list?
No even though you can't add any elements except null
to a type that is parametrized on an unbounded wildcard or an extends wildcard, it is not equivalent of an immutable structure. For example, in case of collections, we can always remove elements from a collection even if it is parametrized on an extends wildcard or on an unbounded wildcard. It is incorrect to rely on wildcard bounding to protect a list from modifications.
Question # 4
What is the put principle?
The put principle is the inverse of the get principle. A list that with the bound List<? super T>
can have elements of type T or its subtypes added to it but never taken out. The only value you can take out of such a list is of type Object since that is the supertype of every type. The put principle says to use a super wildcard when you only want to put values into a structure. Consider the snippet below:
List<? super Number> listOfNumbers = new ArrayList<>();
Integer i = Integer.valueOf(5);
Double d = Double.valueOf(5);
// Adding an integer
listOfNumbers.add(i); // Allowed
// Adding a double
listOfNumbers.add(d); // Allowed
i = listOfNumbers.get(0); // <--- Compile time error
Object object = listOfNumbers.get(0); // Allowed
We can only retrieve Objects from the list as shown above though we may subsequently cast the retrieve object to another type at our own peril. In summary, think of ? super T as containing every type in an interval bounded by T below and by Object above.
import java.util.*;class Demonstration {public static void main( String args[] ) {List<? super Number> listOfNumbers = new ArrayList<>();Integer i = Integer.valueOf(5);Double d = Double.valueOf(5);// Adding an integerlistOfNumbers.add(i); // Allowed// Adding a doublelistOfNumbers.add(d); // Allowed// i = listOfNumbers.get(0); // <--- Compile time errorObject object = listOfNumbers.get(0); // Allowed}}
Question # 5
What to do when we want to both get and put values in a structure?
In situations where you want to create a structure to both put values into and get values out of, you shouldn't use the wildcard. We can't combine the wildcard with both extends and super in the same expression e.g. <? extends Number super Double>
isn't legal though there may be situations where you would want something similar.