Java Generics: The Difference between Java Arrays and Generic Lists

In this post we'll investigate generic lists and how they differ from Java arrays.

A puzzle

Let's begin with a puzzle. Suppose we have a class Student which inherits from a class Person:

public class Person { ... }

public class Students extends Person { ... }

We have the following variables:

List<Person> personList;
List<Student> studentList;
Person[] personArray;
Student[] studentArray;

Which of the following assignments is correct?

studentList = personList;
personList = studentList;
studentArray = personArray;
personArray = studentArray;


studentList = personList;

Answer: incorrect.
This one is obvious to anyone. Surely a list of Persons cannot be assigned to a variable containing a list of Students because the list can contain Person objects which are not Students.

personList = studentList;

Answer: incorrect.
This assignment is not correct because List<Student> is not a subclass of List<Person>. This is surprising to many Java beginners. Here's an explanation. Imagine for a moment that the assignment was correct. Then the variable personList would hold a reference to the studentList object (a list of Students). But the variable personList only knows that it is a list of Persons. One can try to insert a Professor into the list.

personList.add(new Professor("A","B")); //incorrect but compiles

The compiler does not know that the insert operation is incorrect. The runtime system has no way of knowing that the insert is incorrect. Recall from my previous post that at runtime the type of studentList object is List. All objects, including Professor, can be put into something of type List.

If List<Student> were a subclass of List<Person>, then the generics would not ensure type safety as it is supposed to guarantee.

studentArray = personArray;

Answer: incorrect.
This assignment is not correct, for the same reasons as the similar assignment with lists is not correct.

personArray = studentArray;

Answer: correct.
Here we have another surprise. Arrays in Java are covariant, which, in our example, means that if Student is a subclass of Person (which it is), then Student[] is a subclass of Person[].

Arrays are covariant

Why is the covariance allowed for arrays and forbidden for parametrized lists? Are the two that much different? Java arrays know the type of its elements and they check at runtime whether an element can be inserted into it.
personArray[0] = new Professor("A","B"); //runtime exception

If we try to insert a Professor into an array of Students (even if it's referenced via a Person[] variable) an ArrayStoreException will be thrown.

Again, it's not possible that an exception is thrown in a similar situation with parametrized lists. The generic lists do not know the type of its elements at runtime. They cannot check whether an element can be inserted into the list. As a consequence, List<Person> is not a subclass of List<Student>.

Lists are not covariant

It is possible to have a variable which can hold both a list of Persons and a list of Students but you have to use a new construction, wildcards. But that's a topic for a different post.

Have you ever been puzzled by generic collections?