Implications for Arrays – Generics

Implications for Arrays

Array store checks are based on the element type being a reifiable type, in order to ensure that subtype covariance between array types is not violated at runtime. In the code below, the element type of the array is String and the array store check at (1) disallows the assignment, resulting in an ArrayStoreException because the reference value of a Double cannot be stored in a String reference.

Click here to view code image

String[] strArray = new String[] {“Hi”, “Hello”, “Howdy”};
Object[] objArray = strArray; // String[] is a subtype of Object[]
objArray[0] = 2020.5;         // (1) ArrayStoreException

We cannot instantiate a formal type parameter, nor can we create an array of such a type:

Click here to view code image

// T is a formal type parameter.
T t = new T();                // Compile-time error!
T[] anArray = new T[10];      // Compile-time error!

It is also not possible to create an array whose element type is a concrete or a bounded wildcard parameterized type:

Click here to view code image

// An array of Lists of String
List<String>[] list1 = {                           // Compile-time error
  Arrays.asList(“one”, “two”), Arrays.asList(“three”, “four”)
};
List<String>[] list2 = new List<String>[] {        // Compile-time error
  Arrays.asList(“one”, “two”), Arrays.asList(“three”, “four”)
};
// An array of Lists of any subtype of Number
List<? extends Number>[] list3
               = new List<? extends Number>[] {    // Compile-time error
  Arrays.asList(20.20, 60.60), Arrays.asList(1948, 1949)
};

Unbounded wildcard parameterized types are allowed as element types because these types are essentially equivalent to the raw types (p. 584):

Click here to view code image

List<?>[] list4 = {
  Arrays.asList(“one”, “two”), Arrays.asList(“three”, “four”)
};
List<?>[] list5 = new List<?>[] {
  Arrays.asList(20.20, 60.60), Arrays.asList(1978, 1981)
};
List[] list6 = list5;

Note that we can always declare a reference of a non-reifiable type. It is creating arrays of these types that is not permitted.

Click here to view code image

class MyIntList extends ArrayList<Integer> {}     // A reifiable subclass.
// Client code
List<Integer>[] arrayOfLists = new MyIntList[5];  // Array of Lists of Integer
List<Integer[]> listOfArrays = new ArrayList<>(); // List of Arrays of Integer

The class MyStack<E> in Example 11.10, p. 598, implements a method to convert a stack to an array:

Click here to view code image

// Copy to array as many elements as possible.
public E[] toArray(E[] toArray) {                                // (11)
  Node<E> thisNode = tos;
  for (int i = 0; thisNode != null && i < toArray.length; i++) {
    toArray[i] = thisNode.getData();
    thisNode = thisNode.getNext();
  }
  return toArray;
}

Note that the array is passed as a parameter because we cannot create an array of the parameter type, as the following version of the method shows:

Click here to view code image

public E[] toArray2() {
  E[] toArray = new E[numOfElements];            // Compile-time error
  int i = 0;
  for (E data : this) { toArray[i++] = data; }
  return toArray;
}

The third version below uses an array of Object. The cast is necessary in order to be compatible with the return type. However, the cast is to a non-reifiable type, resulting in an unchecked cast warning:

Click here to view code image

public E[] toArray3() {
  E[] toArray = (E[])new Object[numOfElements];  // (1) Unchecked cast warning
  int i = 0;
  for (E data : this) { toArray[i++] = data; }
  return toArray;
}

The method implementation above has a serious problem, even though the code compiles. We get a ClassCastException at (2) below because we cannot assign the reference value of an Object[] to an Integer[] reference:

Click here to view code image

MyStack<Integer> intStack = new MyStack<>();
intStack.push(9); intStack.push(1); intStack.push(1);
Integer[] intArray = intStack.toArray3();        // (2) ClassCastException

The final and correct version of this method uses reflection to create an array of the right element type:

Click here to view code image

@SuppressWarnings(“unchecked”)
public E[] toArray4(E[] toArray) {
  if (toArray.length != numOfElements) {
    toArray = (E[])java.lang.reflect.Array.newInstance(             // (3)
                         toArray.getClass().getComponentType(),
                         numOfElements);          // Suppressed unchecked warning
  }
  int i = 0;
  for (E data : this) { toArray[i++] = data; }
  return toArray;
}

The method is passed an array whose element type is determined through reflection, and an array of this element type (and right size) is created at (3). The method newInstance() of the Array class creates an array of specified element type and size. The element type is looked up through the class literal of the array supplied as an argument. The unchecked cast warning is suppressed because we know it is unavoidable. We will not go into the nitty-gritty details of using reflection here.

The client code now works as expected. We pass an array of zero length, and let the method create the array.

Click here to view code image

MyStack<Integer> intStack = new MyStack<>();
intStack.push(9); intStack.push(1); intStack.push(1);
Integer[] intArray = intStack.toArray4(new Integer[0]);             // OK.

The next example demonstrates the danger of casting an array of a reifiable type to an array of a non-reifiable type. An array of the raw type List (reifiable type) is created at (1), and cast to an array of List<Double> (non-reifiable type). The cast results in an unchecked cast warning. The first element of the array of List<Double> is initialized with a list of Double at (2). The reference value of this array is assigned to a reference of type List<? extends Number> at (3). Using this reference, the list of Double in the first element of the array is replaced with a list of Integer at (4). Using the alias arrayOfListsOfDouble of type List<Double>[], the first element in the first list of the array (an Integer) is assigned to a Double reference. Since the types are incompatible, a ClassCastException is thrown at (5). Note that the array store check at (4) succeeds because the check is against the reified element type of the array, List, and not List<Double>.

Click here to view code image

List<Double>[] arrayOfListsOfDouble
               = (List<Double>[]) new List[1];    // (1) Unchecked cast warning!
arrayOfListsOfDouble[0] = Arrays.asList(10.10);   // (2) Initialize
List<? extends Number>[] arrayOfListsOfExtNums = arrayOfListsOfDouble; // (3)
arrayOfListsOfExtNums[0] = Arrays.asList(10);     // (4) Array storage check ok
Double firstOne = arrayOfListsOfDouble[0].get(0); // (5) ClassCastException!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *