I'm studying generics in this period and today I've found this mystery for me.
Let's consider the following dummy class:
public class Main{
public static void main(String[] args) {
Container<Integer> c = new Container<Integer>();
c.getArray(); //No Exception
//c.getArray().getClass(); //Exception
//int a = c.getArray().length; //Exception
}
}
class Container<T> {
T[] array;
@SuppressWarnings("unchecked")
Container() {
array = (T[])new Object[1];
}
void put(T item) {
array[0] = item;
}
T get() { return array[0]; }
T[] getArray() { return array; }
}
Because of erasure, at runtime, the T[] return type of the getArray() method is turned into a Object[], which is completely reasonable to me.
If we access that method as it is (c.getArray()) no Exceptions are thrown, but if we try to call some methods on the returned array, for example c.Array().getClass(), or if we try to access to a field, for example c.getArray().length, then the following exception is thrown:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
Why is this Exception thrown? Why is it not thrown also for the simple c.getArray() call? Why is it trying to cast to Integer[] if we are simply calling getClass() or accessing length? Are getClass() and length not available also for Object[]?
Thanks in advance for your many (I hope) and explanatory (I hope this too) answers.
The reason for the exception is that the compiler expects a Integer[]
but receives an Object[]
. It added a run-time cast - at the call sites of getArray
. Those casts discovered the lying, dummy, no-effect cast in your constructor.
For it to be correct, one needs the actual class of T, in order to create instances.
@SuppressWarnings("unchecked")
Container(Class<T> type) {
array = (T[]) Array.newInstance(type, 10);
}
Container<Integer> c = new Container<Integer>(Integer.class);
c.getArray();
Class<?> t = c.getArray().getClass();
System.out.println(t.getName());
int a = c.getArray().length;
Also here remains an "unsafe" cast to T[]
but this is unavoidable as Array.newInstance
is a low-level method for n-dimensional arrays like in:
(double[][][][][][]) Array.newInstance(double.class, 3, 3, 3, 3, 3, 6);
When you do an unsafe unchecked cast, it may or may not cause an exception somewhere. You are not guaranteed to get an exception somewhere.
In this case, whether you get an exception depends on whether the compiler inserted a cast in the erased code to cast the result of the call to Integer[]
. In this case, it seems a cast was inserted in the second and third case but not the first case.
In each of the three cases, the compiler is allowed to insert a cast (because it is allowed to assume that the result is Integer[]
or not insert a cast (because the expression is used in such a way that only requires Object[]
in all three). Whether to insert a cast or not is up to the particular compiler implementation to decide.
Why would this compiler not insert a cast in the first case and insert a cast in the second and third cases? One obvious explanation would be that in the first case, the result is obviously unused, so it is very simple to determine that a cast is unnecessary. In the second and third cases, to determine that a cast is unnecessary would require looking at how the expression is used to see that the it will also work with Object[]
; and this is a rather complicated analysis. The compiler authors probably opted for a simple approach where they skip the cast only when the result is unused.
Another compiler might insert casts in all three cases. And another compiler might have no casts in all three cases. You cannot rely on it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With