0

I am trying to implement a sorted and unsorted array list. Both extend a class called AbstractArrayMyList which contains common operations/implementations - toString, clear, etc....

Here is my code for AbstractArrayMyList(which implements a generic interface I defined)

public abstract class AbstractArrayMyList<E> implements MyList<E> {
        protected E[] elementData;
       .....
}

I chose to make elementData protected so that sorted and unsorted specialized array lists can access and perform operations on it. Here is my declaration/code for sorted array list

public class ArrayListSorted<E extends Comparable<E>> extends AbstractArrayMyList<E> 

This all compiles fine. However when I test my code, with these lines

ArrayListSorted<Integer> toTestInteger = new ArrayListSorted<Integer>()
toTestInteger.insert(0);
assertEquals(toTestInteger.get(0).intValue(), 0);

I get a class cast exception

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Comparable;
    at myarraylist.ArrayListSorted.getIndex(ArrayListSorted.java:38)

that occurs here

@Override
public int getIndex(E value) {
     int lo = 0;
     int hi = size;
     while (lo <= hi) {
         // Key is in a[lo..hi] or not present.
         int mid = lo + (hi - lo) / 2;
         if      (value.compareTo(elementData[mid]) < 0)  hi = mid - 1;

The exception occurs on the same line as the compareTo. Does anyone know what the issue is? I defined the bounded wildcard, E extends Comparable, which means that any class that wishes to work with ArrayListSorted must implement the Comparable interface...

I mean i even have the right syntax, from http://docs.oracle.com/javase/tutorial/java/generics/upperBounded.html, type extends class/interface

committedandroider
  • 8,711
  • 14
  • 71
  • 126

1 Answers1

1

The problem is that you're using the generic type as the type of the array. Array types are reified (actually present in the JVM) at runtime, but the generic types aren't. This means that your new E[] actually ends up being an Object[] instead of an array of the type you wanted.

The standard collections deal with this problem by not providing direct access to the array and casting to E on operations like get(). If you really think that using a typed array is the best option, then you'll need to pass Class<E> clazz to the constructor for your abstract base class and use that to construct a correctly-typed array:

protected AbstractArrayMyList(Class<E> clazz) {
    this.elementClass = clazz;
    this.elementData = Array.newInstance(clazz, INITIAL_SIZE);
}

The reason you're getting the ClassCastException is that the compiler replaces the method signatures with their erasures, which is basically the greatest common denominator of the acceptable types. Since you're narrowing E from Object to Comparable in your subclass, the signature on that method ends up being Comparable[] instead of Object[].

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • So cast to comparable? Or reinitialize it in the sorted array list? – committedandroider Jan 18 '15 at 01:10
  • @committedandroider Do what I said above: pass `E` into the superclass constructor and use that to create the array in the first place. – chrylis -cautiouslyoptimistic- Jan 18 '15 at 01:11
  • from this, "If you really think that using a typed array is the best option" , are you implying there's a better way of doing this? – committedandroider Jan 18 '15 at 01:14
  • @committedandroider ...did you read my answer? I explained how the standard collection classes handle the typing issue. – chrylis -cautiouslyoptimistic- Jan 18 '15 at 01:22
  • Sorry, I m just really confused. so in this line of code, when i initialized elementData, elementData = (E[]) new Object[capacity], what you're saying is that it ends up being an object array still? – committedandroider Jan 18 '15 at 01:30
  • Exactly. You can't create an array of a generic type, because the value of `E` isn't available at runtime. (Note that that code wasn't included in the question.) – chrylis -cautiouslyoptimistic- Jan 18 '15 at 01:31
  • This is also confusing. The reason you're getting the ClassCastException is that the compiler replaces the method signatures with their erasures, which is basically the greatest common denominator of the acceptable types. So inside public int getIndex(E value) of sorted array list , E is being replaced with Comparable? – committedandroider Jan 18 '15 at 01:33
  • That's correct. (Unless you override it in a subclass where you're making `E` more restrictive, in which case it'll be the more restrictive type, such as `Comparable`.) – chrylis -cautiouslyoptimistic- Jan 18 '15 at 01:35
  • So when i make a call to toTestInteger.insert(0), 0 is being interpreted as a Comparable? – committedandroider Jan 18 '15 at 01:39
  • How would it look if it were to do it this approach - by not providing direct access to the array and casting to E on operations like get()? I tried doing this cast (E)elementData[index] but still got a class cast exception – committedandroider Jan 18 '15 at 18:27