Q1. If java.lang.Object is the super type for java.lang.Number and, Number is the super type for java.lang.Integer, am I correct in saying that List<Object> is the super type for List<number> and, List<Number> is the super type for List<Integer>.
A1. No. List<Object> is not the the super type of List<Number>. If that were the case, then you could add objects of any type, and it defeats the purpose of Generics.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* Compile Error: Type mismatch. Cannot convert from ArrayList<Integer> to List<Number>*/ List<Number> numbers2 = new ArrayList<Integer>(); // Compiles List<Integer> numbers3 = new ArrayList<Integer>(); // Compile Error: Can't instantiate with wildcard List<? super Integer> numbers6 = new ArrayList<? super Integer>(); // Compiles List<? super Integer> numbers7 = new ArrayList<Integer>(); numbers7.add(Integer.valueOf(5)); //ok // Compiles List<? extends Number> numbers5 = new ArrayList<Number>(); // Compile error: Read only. Can't add numbers5.add(Integer.valueOf(5)); |
In Generics, wildcards (i.e. ?), makes it possible to work with super classes and sub classes.
Q2. How will you go about deciding which of the following to use?
- <Number>
- <? extends Number>
- <? super Number>
A2. Here is the guide:
1. Use the ? extends wildcard if you need to retrieve object from a data structure. That is read only. You can’t add elements to the collection.
2. Use the ? super wildcard if you need to put objects in a data structure.
3. If you need to do both things (read and add elements), don’t use any wildcard.
Scenario 1: A custom generic class GenericSingleTypeScenario class that handles a given input of type T, where “T” can be any type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import java.util.ArrayList; import java.util.Collection; import java.util.List; public class GenericSingleTypeScenario<T> { List<T> vals = new ArrayList<T>(); public void read(List<? extends T> vals) { for (T val : vals) { System.out.println("read: " + val); } } public void add(T val) { vals.add(val); } public void addAll(Collection<? extends T> val) { vals.addAll(val); } public void addAndRead(T valToAdd) { vals.add(valToAdd); for (T valRead : vals) { System.out.println("readAndWrite: " + valRead); } } public List<T> getValues() { return vals; } } |
Add a test class with a main method to test the above class with “T” being an “Integer”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import java.util.Arrays; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<Integer> singleType = new GenericSingleTypeScenario<Integer>(); singleType.add(1); singleType.read(singleType.getValues()); singleType.add(6); // autoboxes 6 to type Integer Integer[] moreVals = {22, 33}; singleType.addAll(Arrays.asList(moreVals)); singleType.addAndRead(9); } } |
Output:
1 2 3 4 5 6 7 | read: 1 readAndWrite: 1 readAndWrite: 6 readAndWrite: 22 readAndWrite: 33 readAndWrite: 9 |
Scenario 2: It can handle any given input of type Number like Integer, Long, and Double.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import java.util.Arrays; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<Number> singleType = new GenericSingleTypeScenario<Number>(); singleType.add(1.0); singleType.read(singleType.getValues()); singleType.add(6.0); // autoboxes 6.0 to type Double Long[] moreVals = {22L, 33L}; singleType.addAll(Arrays.asList(moreVals)); singleType.addAndRead(9); } } |
Output:
1 2 3 4 5 6 7 | read: 1.0 readAndWrite: 1.0 readAndWrite: 6.0 readAndWrite: 22 readAndWrite: 33 readAndWrite: 9 |
Scenario 3: “T” being a “String”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import java.util.Arrays; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<String> singleType = new GenericSingleTypeScenario<String>(); singleType.add("Apple"); singleType.read(singleType.getValues()); singleType.add("Orange"); String[] moreVals = {"Tomato", "Avacado"}; singleType.addAll(Arrays.asList(moreVals)); singleType.addAndRead("Grapes"); } } |
Output:
1 2 3 4 5 6 7 8 | read: Apple readAndWrite: Apple readAndWrite: Orange readAndWrite: Tomato readAndWrite: Avacado readAndWrite: Grapes |
Scenario 4: “T” being any “Object”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import java.util.Arrays; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<Object> singleType = new GenericSingleTypeScenario<Object>(); singleType.add(1); singleType.add(6.0); Long[] moreVals = {22L, 33L}; singleType.addAll(Arrays.asList(moreVals)); singleType.addAndRead("Apple"); singleType.read(singleType.getValues()); } } |
Output:
1 2 3 4 5 6 7 8 9 10 11 12 | readAndWrite: 1 readAndWrite: 6.0 readAndWrite: 22 readAndWrite: 33 readAndWrite: Apple read: 1 read: 6.0 read: 22 read: 33 read: Apple |
Why use wildcards?
Q3. Why was the read(….) method in “GenericSingleTypeScenario.java” was implemented with wildcard “? extends T” as opposed to just “T” as shown below:
1 2 3 4 5 6 7 | public void read(List<T> vals) { //instead of List<? extends T> for (T val : vals) { System.out.println("read: " + val); } } |
A3. The reason being what if you want to read a List<Double> from that method. If you were to NOT use the wildcard as in “? extends T“ the following code would have a compile error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import java.util.ArrayList; import java.util.List; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<Number> listOfInts = new GenericSingleTypeScenario<Number>(); listOfInts.add(1); // can add Number or subclasses of Number List<Double> numbers = new ArrayList<Double>(); numbers.add(5.0); listOfInts.read(numbers); // Compile Error because 'T' is a Number and // it won't work with a Double } } |
This is the reason why “? extends T” was introduced to be able to read List<Integer>, List<Double>, List<Long>, etc in a generic manner.
Q4. What if you use just use “?” instead of “? extends T”?
A4. The above code would compile, but the read(…) method must be changed to use “Object” as shown below as it could take a List<String> as well.
1 2 3 4 5 6 7 | public void read(List<?> vals) { for (Object val : vals) { System.out.println("read: " + val); } } |
and the following code will compile.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import java.util.ArrayList; import java.util.List; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<Number> listOfInts = new GenericSingleTypeScenario<Number>(); listOfInts.add(1); // can add Number or subclasses of Number List<String> fruits = new ArrayList<String>(); fruits.add("Apple"); listOfInts.read(fruits); } } |
Q5. Where will you apply the wild card “? super T“?
A5. If you were to add the following method to “GenericSingleTypeScenario.java”
1 2 3 4 5 | public void add(List<? super T> list, T val) { vals.add(val); } |
The following code will compile.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import java.util.ArrayList; import java.util.List; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<Integer> listOfInts = new GenericSingleTypeScenario<Integer>(); listOfInts.add(2); List<Number> doubles = new ArrayList<Number>(); doubles.add(5.0); listOfInts.add(doubles, 6); System.out.println(doubles); } } |
Output:
1 2 3 | [5.0, 6] |
The following code without the wildcard “? super T”
1 2 3 4 5 | public void add(List<T> list, T val) { list.add(val); } |
will throw a compile error as shown below as “T” is an “Integer” and List<Number> cannot be assigned to add(…..) method’s first argument List<Integer>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import java.util.ArrayList; import java.util.List; public class GenericSingleTypeScenarioTest { public static void main(String[] args) { GenericSingleTypeScenario<Integer> listOfInts = new GenericSingleTypeScenario<Integer>(); listOfInts.add(2); List<Number> doubles = new ArrayList<Number>(); doubles.add(5.0); listOfInts.add(doubles, 6 // !!!! COMPILE ERROR System.out.println(doubles); } } |
But it will be able to assigned to List<? super Integer> as “Number” is a super class of “Integer”.