This is magic! Look at this simple code:
public class ArrayOFMagic<T> {
protected T[] array;
protected int showMeYouRLength() {
return array.length;
}
ArrayOFMagic() {
array = (T[]) new Object[10];
}
protected void set(T value, int index) {
array[index] = value;
}
public static void main(String[] args) {
ArrayOFMagic<Integer> arrayOFMagic = new ArrayOFMagic<Integer>();
System.out.println(arrayOFMagic.showMeYouRLength());
System.out.println("MAGIC INCOMING");
System.out.println(arrayOFMagic.array.length);
}
}
Output:
10
MAGIC INCOMING
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; at ArrayOFMagic.main(ArrayOFMagic.java:25)
I call array.length two times. Once through method and once directly. It proceeds when using method and throws exception when called directly. O.o Someone explain?
edit: Just to clarify: Class works good when not called directly. You can have setters/getters on array elements and all..!
reflect. Array#newInstance to initialize our generic array, which requires two parameters. The first parameter specifies the type of object inside the new array. The second parameter specifies how much space to create for the array.
ClassCastException is a runtime exception raised in Java when we try to improperly cast a class from one type to another. It's thrown to indicate that the code has attempted to cast an object to a related class, but of which it is not an instance.
Java allows generic classes, methods, etc. that can be declared independent of types. However, Java does not allow the array to be generic. The reason for this is that in Java, arrays contain information related to their components and this information is used to allocate memory at runtime.
You can do this: E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH]; This is one of the suggested ways of implementing a generic collection in Effective Java; Item 26. No type errors, no need to cast the array repeatedly.
UPDATED Answer:
DISCLAIMER: This answer is not from me, I asked a former Sun employee who worked on generics in Java why this happens. His answer:
The first call accesses the member from inside the generic class itself, which is erased to it's lower bound (in this case, Object), so there's no cast in the ref to array.length.
But the second call is on a parameterized instance of a generic type, so the type variable (the array) is bound to Integer.
The array field is declared to be of type T[], which is bound to Integer[]. The accessor code that is emitted by the compiler casts it to that type, and the cast blows (in every sense of the word.)
OLD ANSWER: Here's a better way to implement your class (as an addition to zhong.j.yu's answer).
import java.util.ArrayList;
import java.util.List;
public class NotSoMagical<T> {
public List<T> arrayList;
protected int length() {
return arrayList.size();
}
NotSoMagical() {
arrayList = new ArrayList<T>(10);
}
protected void set(T value, int index) {
arrayList.set(index, value);
}
public static void main(String[] args) {
NotSoMagical<Integer> notMagicalAtAll = new NotSoMagical<Integer>();
System.out.println(notMagicalAtAll.length());
System.out.println("MAGIC INCOMING");
System.out.println(notMagicalAtAll.arrayList.size());
}
}
Ideally, this line should fail immediately at runtime
array = (T[]) new Object[10]; // Object[] cannot be cast to Integer[]
unfortunately, due to erasure, the error is swallowed. The error could pop up later, but when and where? That's undefined. The language spec does not say whether access to arrayOFMagic.array.length
should succeed. See also: Generics Oddity - I can insert a Long value into a Map<String, String> and it compiles and doesn't fail at runtime
Suggestion: do not use T[] array
; use simply Object[] array
.
When using a generic type, the compiler creates hidden casts, so the exception occurs due to wrong array creation. But you may create the array with the right type by changing the constructor using java.lang.reflect.Array:
ArrayOFMagic(Class<T> elementType) {
array = (T[]) Array.newInstance(elementType, 10);
}
It is most instructive to write this without generics. Any program written with generics can be converted to an equivalent program by just removing generics and adding casts. This conversion is called type erasure. After type erasure, what is happening becomes very apparent:
public class ArrayOFMagic {
protected Object[] array;
protected int showMeYouRLength() {
return array.length;
}
ArrayOFMagic() {
array = new Object[10];
}
protected void set(Object value, int index) {
array[index] = value;
}
public static void main(String[] args) {
ArrayOFMagic arrayOFMagic = new ArrayOFMagic();
System.out.println(arrayOFMagic.showMeYouRLength());
System.out.println("MAGIC INCOMING");
System.out.println(((Integer[])arrayOFMagic.array).length);
}
}
(In this case, you could argue that the cast to Integer[]
is unnecessary. That a smart compiler could eliminate it, since Object[]
already has the field named length
. However, as a general rule, whenever you get something from the generic class into the "outside" of the generic scope, it is cast to the appropriate type with the type argument substituted in.)
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