This post is in continuation to the previous post – Co-variance and Contra-variance

Arrays in Java are Co-variant. Which basically means if “String” extends “Object” then “String[]” also extends “Object[]”.
Do you see  any pitfall’s above??

Lets look at the following example

Example – 1

String[] a = new String[]{"Hello","Hey"};
 Object[] b = a;//line 2
b[0] = new Integer(23);//line 3
a[0] =??? 

Now because arrays in Java are co-variant, you can do the above. a[0] now will refer to an Integer which is illegal. To prevent this line3 will throw an ArrayStoreException at runtime. Truly this should actually be caught at compile time. Thanks to co-variance of arrays for behavior. Because for some reason arrays were decided to be co-variant and they are reifiable, i.e. they retain the type information at runtime. Which allowed us to do line-2 but also gave us line-3. To prevent this, they manually had to check the type info for every access of array which again is bad and extra overhead. But why were arrays made co-variant?? Java founders wanted to have Generics right from day 0 (Generics were introduced in Java 5). Generics follow type erasure, i.e. the type is checked for compatibility and erased at compile time. More on it here. Due to lack of time, they had to postpone it. Now without generics a method like this would had been impossible. Unless Arrays were made co-variant.

Arrays.sort(Object[] ob)

The above method takes an object array and sorts them based on the Comparables. Hence a single method will suffice. Without covariance of arrays, they will be a need to write a separate method for each of the Data Type. Hence the decision.

Another consequence is that you cannot instantiate an array of a generic type. Nothing special for generics.

Example - 2
List<String>[] l = new ArrayList<String>[10]();//line 1
Object[] a = l;
List<Integer> l2 = new ArrayList<Integer>();
a[0] = l2;
l[0] = ??? //list of string or integer?

Hence line-1 is illegal. Though arrays with wild-cards are allowed i.e. List<?>. Why is left for user to analyse. Similarly, why Generics were decided to be invariant is again left to the user (Hint: Similar to the above example, try adding any animal (pig) to list of dogs) So now we have seen that writing onto co-variant mutable types always throws one or some other trouble. This gives gives rise to 2 questions:

  1. If Arrays were invariant, would it solve the purpose?
  2. If Arrays were left co-variant, what change can be made to make it correct? Has mutability has got anything to do with it?

Suppose Arrays were invariant, then the bugs would be detected right at compile time as raised in Example -1. Thus Invariance of Arrays looks to be the right way forward. Scala gets this right. Lets look at the second question. Suppose Generics below were co-variant. Lets consider immutable List for example. (By ImmutableList I mean, any addition to the list creates a new List by copying the previous elements and adding the latest element. Similar to CopyOnWriteArrayList just that addition returns a new List every time)

<pre>ImmutableList<String> a = new ImmutableList<String>();
ImmutableList<String> b = a.add("Hey");//line2
ImmutableList<Object> x = a;//line 3
ImmuabltList<Object> y = b.add(new Object);

In Line-2, adding a string returned immutable list containing only “Hey”. ImmutableList “a” still is empty. Now on adding Object in Line-4 gave a new List containing (“Hey” and an “Object”) to become “y”. The return type of list is “Object”. Hence it is safe at all the stages. As now “y” is a list of Objects containing a “Hey” and an “Object.

So it turns out that with immutability, co-variance can be used safely. Com’on Co-variance is such a lovely tool. It lets you do stuff like if A <:  B then it is handy to have C[A] <: C[B]. Isn’t it?

So remember [P2] (from last post). Preferably with rights co-variance can be trouble-some. Perfect for Reads just like returns in functions.

Interestingly Contra-variance is made for writes. More on it and beautiful implements of it in Scala in the next post.