The Java Generics Test Answers

October 10th, 2012 @   - 

Short Answers

  • Legal: 1, 2, 6, 7, 8, 9, 11, 12, 14, 16, 19, 20
  • Illegal: 3, 4, 5, 10, 13, 15, 17, 18

Long Answers

Declarations:

Covariance at the class level:

1)      List<String> list = new ArrayList<String>();

This question starts off the series gently with a subtle attempt to confuse class covariance with generics’ parameterized type covariance. Class covariance is just vanilla object-oriented Java we’re used to: ArrayList is a subtype of List and can therefore be assigned to a variable of type List. Parameterized types too have their own covariance mechanism, expressed with wildcards (?), and it varies independently of class covariance. As far as class covariance is concerned, this statement is fine and nothing in generics changes that fact. The answer to question 1 is legal.

2)      List<String> list= new ArrayList<>();

Java 7 introduced the new diamond operator represented by the <> symbols. This is strictly syntactic sugar and helps only by making common statements like this more concise. The compiler infers than an ArrayList of type String is required. With Java 7, specifying the parameterized type,  <String> in this case, is now optional on the right side of the assignment with Java 7. Inference works even when assigning to bounded or unbounded wildcards. For the sake of conciseness, the diamond operator should be the preferred style going forward. As far as this statement is concerned, it is perfectly legal.

Covariance at the parameterized type level:

3)      List<Object> list = new ArrayList<String>();

4)      ArrayList<Object> list = new ArrayList<String>();

5)      ArrayList<String> list = new ArrayList<Object>();

Questions 3 to 5 assign one concrete parameterized type to a different kind. However, each is a super or sub type of one another. Remember that the raison d’être of generics is type-safety – specifically for the collections library. These statements violate this spirit because they could potentially introduce heterogeneous types within the same collection – even if the types are part of the same hierarchy. Needless to say, all three statements are illegal.

Raw types:

6)      ArrayList list = new ArrayList<String>();

7)      ArrayList<String> list = new ArrayList();

These two questions revolve around the assignment of raw types to generic types and vice-versa. These water down the type-safety guarantee of generics collections to the point where they are no better than non-generic collections. While these will always generate a compilation warning, they are required to maintain backward compatibility when mixing generics with non-generics applications. Both are therefore legal.

Wildcards:

8)      ArrayList<String> list = null;

ArrayList<? extends String> extendsList = list;

9)      ArrayList<?> list = new ArrayList<Number>();

10)   ArrayList<? extends Number> extendsList = null;

ArrayList<? super Number> superList = extendsList;

11)   ArrayList<? super Number> superNumList = null;

ArrayList<? super Integer> superIntList = superNumList;

Questions 8 through 11 deal with unbounded (?) and bounded wildcards(? extends E, ? super E). These allow generics to be covariant and follow the same rules as class-level covariance. The difference lies in the fact that classes are always covariant whereas generics only become covariant via wildcards. The code in question 8 attempts to assign a concrete parameterized type String to something that extends a String. This is akin to assigning a subtype to a super type, which is legal. Question 9 is like assigning a Number to a variable of type Object and is also legal. Question 10 is forbidden because it mixes lower with upper bounded assignment, something that is never allowed. Finally, question 11 is allowed because superNumList is a super parameterized type of superIntList. So questions 8, 9 and 11 are legal while question 10 is illegal.

Generic usage:

Questions 12 to 17 deal with generics usage and are progressively more difficult. They are also the most complex and least intuitive part of generics. You just have to just trust that the design choices made by Gilad Bracha and co. were done in the name of type-safety even when there appears to be an alternate choice that seems to work better. While I shed some light on some of the darkest corners of generic usage in this section, consult  the very best generics guide on the internet for all the gory details.

12)   ArrayList<Object> list = new ArrayList<Object>();

list.add(“Hello”);

The question deals with the method parameters allowed on an Object concrete parameterized type. Here, an ArrayList of type Object is created and a String is added. Even if retrieval methods such as get() will only return an Object, the string is implicitly up-casted to an object and the line compiles and is legal.

 13)   ArrayList<?> list = new ArrayList<String>();

list.add(“Hello”);

While question 13 is similar to question 12, the results are different because list is an unknown type. The list.add() method expects a concrete type and so cannot be called with an unknown type – otherwise anything goes and the ArrayList can contain heterogeneous types of objects. As a rule, any method invoked on an instance of a wildcard parameterized type that expects a specific, non-wildcard type – in this case, add(E), add(int, E) – cannot be invoked and will result in a compile-time error. Specifically, list is a wildcard parameterized type instance and list.add() expects a concrete type and therefore uninvocable. The answer is therefore illegal .

14)   ArrayList<? super Integer> list = new ArrayList<Integer>();

list.add(1);

This question attempts to trip the test taker because question 13 and 14 are similar and should have the same answer. However, the rules are different for upper and lower bounded parameterized types. For lower bounded types, invoking a method that expects a concrete type, such as list.add(), is allowed. In this case, since Integer “1” will always be compatible to any super type of Integer that can be added to list, this statement is perfectly fine. There are limitations, though. Any values retrieved from list (e.g. list.get()) would be as an Object type. Question 14 is legal.

15)   ArrayList<? extends Number> list = new ArrayList<Integer>();

list.addAll(new ArrayList<Integer>());

Question 15 uses an upper bound of type Number. Since list can contain type Number and any subclass of Number, the compiler forbids access to any method whose argument is a parameterized type (e.g. ArrayList.addAll(ArrayList<E>)) because list is a bounded wildcard instance. While this would have been legal had list been a lower bounded parameterized wildcard type, as-is this question is illegal.

 16)   ArrayList<? super Integer> superIntList= new ArrayList<Number>();

ArrayList<? extends Integer> extendsIntList = new ArrayList<Integer>();

superIntList.addAll(extendsIntList);

Question 16 demonstrates that type declarations can be bounded wildcards (lower bounded Integer in this case) and have methods such as ArrayList.addAll(<? Extends Integer>) that expect upper bounded parameterized type. extendsIntList is exactly the right parameterized type for superIntList.addAll() and therefore legal.

 17)   ArrayList<Integer> intList = new ArrayList<Integer>();

intList.add(new Integer(1));

ArrayList<? extends Number> extendsNumList = intList;

Integer integer = extendsNumList.get(0);

This snippet fails to compile only because of the last line in which extendsNumList, an upper bounded wildcard type is assigned to integer of type Integer. For upper bounded wildcards, the return type of any method returning E (such as E ArrayList.get(int)) is E and in this case Number. This fails because of the attempted downcast from Number to Integer. For wildcards, the ArrayList.get() method would only return instances of type Object. On the other hand, had extendsNumList been a lower rather than an upper bounded wildcard, only Object types could have been returned. Question 17 is illegal.

Arrays:

 18)    ArrayList<String>[] twoDimArray = new ArrayList <String>[10];

Simple, non-generic arrays are covariant in Java. That is, you can assign an array of type String to an array of type Object. Additionally, arrays retain runtime meta-data on the array type allowing the JVM to check for array assignment at runtime. If an incompatible type is assigned to an array, an ArrayStoreException is thrown at runtime. However, generics lose this meta-data after compilation through erasure and are converted to raw types. As such, the compiler does not allow the concrete parameterized type of String. The correct declaration would have been  ArrayList<String>[] twoDimArray = new ArrayList <String>[10];  As-is question 18 is illegal.

 19)  Pair<String, String>[] array = new Pair[2];

array[0] = new Pair<String, String>(“1”, “y”);

array[1] = new Pair(true, false);

Question 19 explores what happens when a combination of raw and parameterized Pair types are used inside the same array. This highlights the dangers of mixing the two. This code snippet would compile but result in a runtime exception if the contents of array[1] were retrieved and the Pair.getValue() invoked. This is because the JVM would expect a Pair<String, String> but instead get a Pair<Boolean, Boolean> type. However, these statements are legal.

20)   Pair<?,?>[] array = new Pair<?,?>[2] ;

array[0] = new Pair<Integer,Integer>(0,0);

array[1] = new Pair<String,String>(“Hello”,”There”);

Question 20 demonstrates the correct way of mixing different types inside an array. Here a Pair of Strings is mixed with a Pair of Integers. While these will be returned as Object types, this code is legal.

References:

Comments are closed.