1

Problem

I have a class called

public class HeapClass <E extends Comparable <E>>

There is a method in there called

public void heapSort(E[] arr)

I have a small main method to test it out and want to call the method with a simple array:

Integer[] arr = {3, 2, 1, 4};
        
HeapClass h = new HeapClass();
        
h.heapSort(arr);

However, for some reason I receive an error:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Comparable;
    at HeapClass.heapSort(HeapClass.java:11)
    at Test.main(Test.java:9)

Full Code

Here is the full code for both files:

public class HeapClass <E extends Comparable <E>>
{
    private int lastposition;
    
    private E[] array;
    
    public void heapSort(E[] arr)
    {
        lastposition = arr.length - 1;
        
        array = (E[]) new Object[arr.length * 2];
        
        for (int i = 0; i < arr.length; i++)
            add(arr[i]);
        
        for (int i = 0; i < arr.length; i++)
            array[arr.length - i - 1] = remove();
        
        arr = array;
    }
    
    public void add (E obj)
    {
        lastposition++;
        array[lastposition] = obj;
        trickleUp(lastposition);
    }
    
    public void swap (int from, int to)
    {
        E temp = array[from];
        array[from] = array[to];
        array[to] = temp;
    }
    
    public void trickleUp(int position)
    {
        if (position == 0)
            return;
        
        int parent = (int) Math.floor((position - 1) / 2);
        
        if (((Comparable <E>) array[position]).compareTo(array[parent]) > 0)
        {
            swap(position, parent);
            trickleUp(parent);
        }
    }
    
    public E remove()
    {
        E temp = array[0];
        swap(0, lastposition--);
        trickleDown(0);
        return temp;
    }
    
    public void trickleDown(int parent)
    {
        int left = 2 * parent + 1;
        int right = 2 * parent + 2;
        
        if (left == lastposition && ((Comparable <E>) array[parent]).compareTo(array[left]) < 0)
        {
            swap(parent, left);
            return;
        }
        if (left == lastposition)
            return;
        if (right == lastposition)
        {
            E max;
            int pos;
            if (((Comparable <E>) array[left]).compareTo(array[right]) < 0)
            {
                max = array[right];
                pos = 1;
            }
            else
            {
                max = array[left];
                pos = 0;
            }
            
            if(((Comparable <E>) array[parent]).compareTo(max) < 0)
            {
                if (pos == 0)
                {
                    swap(parent, left);
                    return;
                }
                else
                {
                    swap(parent, right);
                    return;
                }
            }
            return;
        }
        if (left >= lastposition || right >= lastposition)
            return;
        if (((Comparable <E>) array[left]).compareTo(array[right]) > 0 && ((Comparable <E>) array[parent]).compareTo(array[left]) < 0)
        {
            swap(parent, left);
            trickleDown(left);
        }
        if (((Comparable <E>) array[parent]).compareTo(array[right]) < 0)
        {
            swap(parent, right);
            trickleDown(right);
        }
    }

}
public class Test
{
    public static void main (String[] args)
    {
        Integer[] arr = {3, 2, 1, 4};
        
        HeapClass h = new HeapClass();
        
        h.heapSort(arr);
        
        for (int i = 0; i < arr.length; i++)
            System.out.println(arr[i]);
        
    }
}
Zabuzard
  • 25,064
  • 8
  • 58
  • 82
Brian O
  • 109
  • 1
  • 9
  • 2
    Could you share the full code and the actual error messages? At first glance it looks like it should work. That said, you probably intended to have the `heapSort` method `static` and the generic defined on the method, not on the class. Otherwise you need to create an instance of the class first and call the method on that. As in `HeapClass heapClass = new HeapClass<>();` and then `heapClass.heapSort(arr);` versus just being able to write `HeapClass.heapSort(arr)`. – Zabuzard May 11 '22 at 19:19
  • Voting to close, as its lacking the necessary info and details to spot the issue. – Zabuzard May 11 '22 at 19:23
  • 1
    Please share the full code and the error messages. Would really help to understand the problem. – Brian Brix May 11 '22 at 19:24
  • 1
    I added these corrections. Can you please re-open the question @Zabuzard? Thanks. – Brian O May 11 '22 at 20:07
  • 2
    Voted to reopen. The issue is that you are using your class with raw-types (you should never do this). I.e. you are writing `List list = new List();` instead of specifying generics `List list = new List<>();`. But with `HeapClass` in your case. So you should write `HeapClass h = new HeapClass<>();`. And since you left out the generics, Java falled back on `Object`, which is not `Comparable`, hence the error. – Zabuzard May 11 '22 at 20:08
  • Ok, I fixed that and declared my object as you stated, and I'm getting: ```Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Comparable; at HeapClass.heapSort(HeapClass.java:11) at Test.main(Test.java:9) ``` And Line 11 is: `array = (E[]) new Object[arr.length * 2];` How would I correct something like this? – Brian O May 11 '22 at 20:12
  • You can not cast it to `E[]`. Just keep it `Object[]` internally and cast the individual objects to `E` once needed. This is a language restriction and its kinda unfortunate that you can not _really_ create generic arrays. – Zabuzard May 11 '22 at 20:15
  • @Zabuzard I'm not sure what this means for my program. Should I re-do the entire project? I saw a professor use part of this code (from a video he posted to his class) (which is where I got `E[] array = (E[]) new Object[arr.length];` in the first place. – Brian O May 11 '22 at 20:20
  • Reopened. No "debugging details" are necessary here. The exception is easily reproducible and expected behavior. – BadZen May 11 '22 at 21:04
  • `array = (E[]) new Object[arr.length * 2];` Are instances of class `Object` also `E`? (Hint: no, no they are not - for almost every type `E`). If you ever want to cast to a generic type, you are very likely using generics wrong! – BadZen May 11 '22 at 21:06
  • Don't cast to `Comparable`, cast to `E`: `E` is already `Comparable`. Then, when you compare element, write `((E)array[left]).compareTo(((E)array[right]))` (you could do like ArrayList and have `E get(int index) {return (E) array[index];}`) and use `Object[]` for the array. – NoDataFound May 11 '22 at 21:13
  • @NoDataFound - No, do not write anything like that. Please don't ever tell anyone to write anything like that. That line of code is tortured and cursed. This is not how to use generics in Java. – BadZen May 11 '22 at 21:22
  • What are speaking about? I mainly quoted the code from the JDK which is neither tortured neither cursed (generic and arrays may be, however): https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/ArrayList.java#L136 and https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/ArrayList.java#L441 – NoDataFound May 11 '22 at 21:24
  • And doing `get(index).compareTo(get(otherIndex))` is far easier to read than having the cast in the cast: `((Comparable)array[index]).compareTo((Comparable)array[otherIndex]))`. – NoDataFound May 11 '22 at 21:25
  • The OpenJDK codebase is of course known to be both tortured and cursed, and this is the least of its worries. In any case, your parenthetical edit is the Right Way. :p – BadZen May 11 '22 at 21:28
  • The code in its current state uses the *raw type* `HeapClass`. – MC Emperor May 11 '22 at 21:32
  • @MCEmperor The problem will still occur if you specify the type argument. – BadZen May 11 '22 at 21:35
  • 1
    Okay, but that still needs to be fixed – MC Emperor May 11 '22 at 21:36
  • Ok so I edited my code some. I got it to compile and the only errors left are null pointer exceptions which probably mean I’m comparing things that don’t exist? – Brian O May 11 '22 at 21:42
  • 4
    Generics and arrays have some difficult interactions, because generics are invariant whereas arrays are covariant. If you change your class declaration to `class HeapClass>` it will work. – Brian Goetz May 11 '22 at 22:14

1 Answers1

0

If we take the algorithm you posted and the initial java.lang.ClassCastException error, it is because of a subtle thing:

array = (E[]) new Object[arr.length * 2];

In your class, you asked for E to be an instance of Comparable<E> and declared array to be a E[] and that's the trick: the type of E is bound to Comparable.

So, when you erase type - at least with this online compiler - the type of E, and array, is Comparable.

The JLS 4.6 says:

The erasure of a type variable (§4.4) is the erasure of its leftmost bound.

In your case, the leftmost bound is Comparable and you need to use it to create your array:

array = (E[]) new Comparable[arr.length * 2];

You can view the result here: https://onlinegdb.com/fA_B7xZYD

The resulting NullPointerException is however due to your code not handling the fact that some values may not be set:

  • ArrayList use an array of some length and also use a size: you could mimic that.
  • You could fill in default non null values... which works only for some type (eg: 0 for Integer, "" for String, ...).
  • You could also ignore the null using a Comparator ignoring nulls or wrap your compareTo calls so to avoid nulls.

With a Comparator:

Comparator<E> comparator = Comparator.nullsLast(Comparator.naturalOrder());

And rather than doing:

array[index].compareTo(array[otherIndex])

Do:

comparator.compare(array[index], array[otherIndex])

Or by ignoring nulls, and simplifying the array index access:

int compareIndex(int ia, int ib) {
  E va = array[ia];
  E vb = array[ib];
  if (va == null) {
    if (vb == null) return 0;
    return 1; // a should be after b (nullsLast)
  }
  if (vb == null) return -1; // a should be before a
  return a.compareTo(b);
}

But as I pointed to BadZen, this depends on your assignment: if you are learning, such exercise may help understand the differences between various kind of List (ArrayList, LinkedList, ...) and more generally common structural type found in algorithm.

And given your code, you are doing something that mostly resemble to what a TreeSet provides: a sorted container.

Your code is simply doing it using a sort which I don't know the name in English (bubble sort?) while TreeSet use a B-tree.

NoDataFound
  • 11,381
  • 33
  • 59