Java Generics with extends and super Wildcards and the Get and Put Principle
- Details
- Written by Nam Ha Minh
- Last Updated on 14 June 2019   |   Print Email
1. Understand the extends wildcards in Java Generics
Suppose that we have a method that calculates sum of numbers in a collection as shown below:public static double sum(Collection<Number> numbers) { double result = 0.0; for (Number num : numbers) result += num.doubleValue(); return result; }As explained in this article, with this method signature, we can only pass List<Number>, not List<Double> nor List<Integer>. If we attempt to call this method by passing a list of integers like this:
List<Integer> integers = Arrays.asList(2, 4, 6); double sum = sum(integers);The compiler will issue an error:
sum(java.util.Collection<java.lang.Number>) in GenericsWildcards cannot be applied to (java.util.List<java.lang.Integer>)So how can we pass a collection of subtypes of Number?In this case, the extends wildcard is the answer. This wildcard has the notion of <? extends E>. Let’s update the sum() method like this:
private static double sum(Collection<? extends Number> numbers) { double result = 0.0; for (Number num : numbers) result += num.doubleValue(); return result; }The compile is now happy and allows us to pass a collection of a subtype of Number, as illustrated in the following code snippet:
List<Integer> integers = Arrays.asList(2, 4, 6); double sum = sum(integers); System.out.println("Sum of integers = " + sum); List<Double> doubles = Arrays.asList(3.14, 1.68, 2.94); sum = sum(doubles); System.out.println("Sum of doubles = " + sum); List<Number> numbers = Arrays.<Number>asList(2, 4, 6, 3.14, 1.68, 2.94); sum = sum(numbers); System.out.println("Sum of numbers = " + sum);
Sum of integers = 12.0 Sum of doubles = 7.76 Sum of numbers = 19.76However, an important rule you need to keep in mind about the extends wildcard is that you cannot add elements to the collection declared with the extends wildcard. For instance, if we attempt to add an element like this:
List<? extends Number> numbers = new ArrayList<Integer>(); numbers.add(123); // COMPILE ERRORThe compiler will throw an error! Otherwise, we could add a double number to a collection which is designed to accept only integer numbers. You got this rule? In addition, here are other few noteworthy points with regard to the extends wildcard:- The keyword extends in the context of a wildcard represents both subclasses and interface implementations. For example:
// Serializable is an interface List<? extends Serializable> list = new ArrayList<Integer>();- List<?> is the abbreviation for List<? extends Object>. They are absolutely identical.- You cannot add anything into a type declared with an extends wildcard, except null- which belongs to every reference type:
List<? extends Number> numbers = new ArrayList<Integer>(); numbers.add(null); // OK numbers.add(357); // Compile error
2. Understand the super wildcard in Java generics
Suppose that we have the following method that adds N integer numbers into a collection written like this:public static void append(Collection<Integer> integers, int n) { for (int i = 1; i <= n; i++) { integers.add(i); } }And we have a list of numbers, in which we want to add some integers to:
List<Number> numbers = new ArrayList<Number>();How can we pass this collection of Numbers to the append() method above?As the append() method accepts a Collection<Integer>, it’s illegal to pass a List<Number>. The extends wildcard cannot be used like this:
public static void append(Collection<? extends Integer> integers, int n)because Number is supertype of Integer.So what if we want to restrict the elements being added in the method is of type Integer, whereas we also want to accept a collection of super types of Integer, since adding integers to a collection of numbers is perfectly legal?In this case, the super wildcard is the solution. Let’s update signature of the append() method like this:
public static void append(Collection<? super Integer> integers, int n) { for (int i = 1; i <= n; i++) { integers.add(i); } }Then it’s legal to pass a list of numbers like this:
List<Number> numbers = new ArrayList<Number>(); append(numbers, 5); numbers.add(6.789); System.out.println(numbers);The compiler is happy, and the following output is produced:
[1, 2, 3, 4, 5, 6.789]With the super wildcard, we can also pass a list of Objects like this:
List<Object> objects = new ArrayList<Object>(); append(objects, 3); objects.add("Four"); System.out.println(objects);Output:
[1, 2, 3, Four]So far I hope you’ve got the ideas behind the super wildcard. Here are other noteworthy points with regard to a type declared with a wildcard <? super T>:- It can accept any type that is super type of T.- We can add elements to the collection. However the type is restricted to only T.- It cannot be passed to a method having a parameter declared with the extends wildcard. The following code snippet illustrates this point:
public static double sum(Collection<? extends Number> numbers) { double result = 0.0; for (Number num : numbers) result += num.doubleValue(); return result; } List<? super Integer> integers = new ArrayList<Integer>(); sum(integers); // COMPILE ERROR!Why does the compiler prevent this? Consider the following code:
List<Object> objects = new ArrayList<Object>(); objects.add("Hello"); integers = objects; // OK sum(integers); // Compile errorImagine if the compiler accepts, the sum() method would calculate sum of a String, which makes non-sense!To learn more in depth about Java Generics, I recommend you to read the book Java Generics and Collections by Maurice Naftalin and Philip Wadler
3. Understand the Get and Put Principle in Java Generics
So far we have grasped the core concepts of the extends and super wildcards with generics. It may be a good practice to use wildcards where you can in a signature, since this permits the widest range of calls. But how do we decide which wildcards to use?- When should we use the extends wildcard?
- When should we use the super wildcard?
- Where is it inappropriate to use a wildcard at all?
Fortunately, there’s a simple principle that helps us make decision easily. It’s the Get and Put Principle. This principle states that:- Use an extends wildcard when you only get values out of a structure.
- Use a super wildcard when you only put values into a structure.
- And don’t use a wildcard when you both get and put.
- You cannot put anything into a type declared with an extends wildcard except for the value null, which belongs to every reference type.
- You cannot get anything out from a type declared with a super wildcard except for a value of type Object, which is a super type of every reference type.
public static double sumNumbers(Collection<Number> numbers, int n) { append(numbers, n); return sum(numbers); }This method calculates sum of a given N number of numbers. As the sum() method gets values out of the collection, and the append() method puts values into the collection, the sumNumbers() method should declare its first parameter as a collection of type Number, which satisfies both of constraints specified by the sum() and append() method.The following code is a sample call:
Collection<Number> numbers = new ArrayList<Number>(); double sumOfTen = sumAppend(numbers, 10); System.out.println("Sum = " + sumOfTen);And the output is:
Sum = 55.0That's how to use the extends and super wildcards in Java generics.
Related Java Generics Tutorials:
- What are Generics in Java
- How to write generic classes and methods in Java
- Generics with Subtyping and the Substitution Principle
Other Java Collections Tutorial:
- Java List Tutorial
- Java Set Tutorial
- Java Map Tutorial
- Java Queue Tutorial
- Java Stream API Tutorial
- Understand equals and hashCode in Java
- Understand object ordering in Java
Comments