4

Who can explain what is happening in the following scenarios? Why does one give an error, and the other doesn't?

public class TestClass<T extends Comparable<T>> {
    protected T []items;

    public TestClass(int size, T... values) {
        items = (T[]) new Object[size];
        for (int v = 0; v < Math.min(size, values.length); v++) {
            items[v] = values[v];
        }
    }

    public T getItem() {
        return items[0];
    }

    public static void main(String []args) {
        System.out.println(new TestClass<>(2, 6).getItem()); // Error
    }
}

Executing the above class gives the following error:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Comparable;
    at TestClass.<init>(TestClass.java:5)
    at TestClass.main(TestClass.java:16)
Java Result: 1

And there is this:

public class TestClass<T> {
    protected T []items;

    public TestClass(int size, T... values) {
        items = (T[]) new Object[size];
        for (int v = 0; v < Math.min(size, values.length); v++) {
            items[v] = values[v];
        }
    }

    public T getItem() {
        return items[0];
    }

    public static void main(String []args) {
        System.out.println(new TestClass<>(2, 6).getItem()); // Prints 6
    }
}

I should also mention that the creation of the array is done in a super class, so I cannot change the way the array initialization is done. Also this is compiled under Java 8


The solution I went with was to do this: this.items = (T[])new Comparable[size];

The above only works so long as you are not trying to use the array outside your class. So doing this for example is an error:

TestClass<Integer> t = new TestClass<>(2, 6);
System.out.println(t.items[0]); // error more ClassCastException

But doing this isn't:

System.out.println(new TestClass<>(2, 6).getItem()); // Prints 6

Anyone else get the feeling that java generic types are a tad bit inconsistent?

user207421
  • 305,947
  • 44
  • 307
  • 483
smac89
  • 39,374
  • 15
  • 132
  • 179

3 Answers3

2

I do believe your problem lies here

items = (T[]) new Object[size];

The class Object[] does not extend Comparable(or any interface for that matter) and as such, just you cannot say Comparable c = (Comparable)(new Object()), you also cannot cast an Object[] to a Comparable[] if it was not originally one.

kirbyquerby
  • 735
  • 5
  • 16
  • I thought due to type erasure, this should have been handled by the JVM? What do you suggest as a way to solve this? – smac89 Jan 29 '15 at 00:31
  • 1
    Probably the simplest way I can think of would be to follow the fashion that java's ArrayList uses; rather than have a T[], have an Object[](or since the values are Comparables, a Comparable[]), and add the elements to that array. When you want to retrieve data from the array, just retrieve the content and cast it to a T – kirbyquerby Jan 29 '15 at 00:36
  • See my edit, I cannot change the type of the array since I have no access to this – smac89 Jan 29 '15 at 00:40
  • The only way, then, would be to do the other way (which I don't really like but works better in this case), which is what rgettman did, and use Array.newInstance() with the runtime Class of the object you're making an array of passed in as a parameter, and a cast. – kirbyquerby Jan 29 '15 at 00:43
  • What did you mean by this?: "I do not have access to the array creation method?" – kirbyquerby Jan 29 '15 at 00:55
  • I mean that, the array is created in a super class. I didn't post the super class but just know that everything being done in the constructor is actually done in a super class. I just put it there for reference – smac89 Jan 29 '15 at 00:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/69777/discussion-between-kirbyquerby-and-smac89). – kirbyquerby Jan 29 '15 at 01:00
1

Updated in response to a change in the question

If there is an upper bound declared on the generic type parameter, the compiler will insert a cast to the correct type when a method that returns a generic type is called, or something is assigned to a variable of the generic type, to ensure type safety.

In the first case, because Comparable is the upper bound for T, the compiler inserts a cast to Comparable[] in the constructor, because the array is assigned to a T[]. But, what is being assigned is really an Object[], so the cast fails.

In the second case, there is no upper bound, so the compiler doesn't insert a cast to Comparable[] in the constructor, so there is no cast to fail.

To make the first case succeed, even with an upper bound declared on the generic type parameter, you should follow the advice of How to create a generic array in Java? to create your generic array. This requires the Class object to be passed in, so that the array of the proper class is created.

@SuppressWarnings("unchecked")
public TestClass(int size, Class<T> clazz, T... values) {
    items = (T[]) Array.newInstance(clazz, size);
    // ...
}
Community
  • 1
  • 1
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • Actually if you look closely, both are returning type `T`. If that helps – smac89 Jan 29 '15 at 00:33
  • @Smac89 I looked too fast. I have corrected the location where the cast is inserted. – rgettman Jan 29 '15 at 00:41
  • OK got it. But as I just mentioned in my initial post, I do not have access to the array creation method because this class is actually subclassing another class and it is the super class that creates the array. I will look around for other methods – smac89 Jan 29 '15 at 00:46
  • @Smac89 Updated in response to you updating the question. – rgettman Jan 29 '15 at 00:57
  • I was reading to fast as well and missed your first paragraph. It seems that doing `this.items = (T[])new Comparable[cap];` has fixed the problem – smac89 Jan 29 '15 at 02:14
1

If you are okay with (T[]) new Object[size] when T's upper bound is Object, then when T extends Comparable<T>, the analogous thing would be:

(T[]) new Comparable[size]

The reason for this is that T is erased to its upper bound. So in the case when T's upper bound is Object, the cast is erased to a cast to Object[], which is okay. However, when T's upper bound is Comparable<...>, the cast is erased to a cast to Comparable[], which is not okay if the object's runtime class is Object[]. Changing it to Comparable[] fixes this.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • I believe I already resolved this in the original post. However there are still some things I found that are not yet fixed, if you can take a look – smac89 Jan 29 '15 at 19:55
  • @Smac89: Yes, this is the danger with falsely pretending the array is `T[]` when it is not -- `T` is erased inside the class to its upper bound, so it does not cause any problems inside the class. But you cannot expose it to the outside of the class where they assume a concrete type argument for `T`. Without knowing `T` at runtime, it is impossible to expose the correct type of array to the outside of the class. – newacct Jan 30 '15 at 18:56