7

I've encountered a problem while doing a homework for online algorithms class. Casting Object[] to T[] where T is Comparable raises a run-time exception

public  static <T extends Comparable<? super T>> void mergeSort(T[] xs) {
    T[] aux = (T[]) new Object[xs.length];
    mergeSort(xs, aux, 0, xs.length);
}

I could rewrite it in C# which doesn't have any problems with creating generic arrays but I'd rather learn how to deal with this in Java.

1

3 Answers 3

11

If you're getting a runtime exception, it means that the objects you tried to cast don't acutally have that type. Language doesn't have anything to do with it. There's probably a bug in your code.

Edit: It sounds like you are confused by how Java's type system works. In C#, generics actually represent different types at runtime. In Java, generic types don't exist at runtime. They are only a convenience to enable better compile time type checking. During compilation, generics are replaced by a real type in a process known as type erasure.

Normally, the erasure of a generic type is Object, but since you provided an upper bound for T, it is converted to that bound, Comparable. Therefore, after erasure, your code looks like this.

Comparable[] aux = (Comparable[]) new Object[xs.length];

In otherwords, you're creating an array of type Object[] and immediately trying to cast it to type Comparable[]. Since Object doesn't implement Comparable, the types are incompatible, so you get a runtime exception. You can fix this by creating an array of Comparables instead.

public  static <T extends Comparable<? super T>> void mergeSort(T[] xs) {
    T[] aux = (T[]) new Comparable[xs.length];
    mergeSort(xs, aux, 0, xs.length);
}
Sign up to request clarification or add additional context in comments.

2 Comments

Obviously there's a bug and obviously it has something to do with the way Java handles generics. In C# there's no problem with creating arrays of constrained type parameter.
@synapse I've edited my answer to hopefully clarify things for you.
3

Try this:

public  static <T extends Comparable<? super T>> void mergeSort(T[] xs) {
    T[] aux = (T[])java.lang.reflect.Array.newInstance(xs.getClass().getComponentType(), xs.length);
    mergeSort(xs, aux, 0, xs.length);
}

4 Comments

There's a much simpler way to do this. See my answer.
Simpler but dangerous. You can also just change the type like this: <T extends Object & Comparable<? super T>> and it will compile and run even with the original code (depends on the signature of the mergeSort method). But for example if T is always String and you try to do String[] aux2 = (String[]) aux you will get an exception. Your Comparable array has the same problem. So it is simpler but it may not work all the time.
@Jdb That's not how generics work. Even if T is always String, the actual cast it will do is still (Comparable[]) because that's the only bound the compiler cares about (specifically, it's the leftmost bound, so it'd be Object[] in your example). I don't know of any way you could get a runtime exception from pure Java code using this unless you have an unsafe cast somewhere.
It's theoretically possible that the child mergeSort is doing something weird which could cause an exception, but there's no way to know. Based only on the code posted, my suggestion is safe at any rate. The main danger is if the created array is returned to something that knows the actual value of T, but that's not happening here.
2

Arrays are covariant and this means that they retain the type of their elements at runtime. Java's generics are not. So basically they don't mix.

See also: Generic arrays in Java

You cannot create arrays of generics and you cannot cast to them. Better to use arraylists.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.