This is the code: A simple ceneric class and trying to assign an integer to aa[0]
.
public class GenericTest<T> {
T [] aa = (T[]) new Object[2];
T bb;
public GenericTest(T x, T y) {
aa[0] = x; aa[1] = y;
System.out.println(aa[0] + " " + aa[1]); //OK
}
static public void main(String[] args) {
GenericTest<Integer> ll = new GenericTest<>(1,2);
ll.bb = 1; // OK
ll.aa[0] = 6; // ClassCastException from Object to Integer
}
}
In fact, the exception message is this:
java.lang.ClassCastException:
[Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
It is saying that it can't cast an Object[]
to an Integer[]
.
The root cause of is the initializer in:
T [] aa = (T[]) new Object[2];
That typecast is an unsafe typecast. And indeed the compiler tells you that something is wrong:
$ javac GenericTest.java
Note: GenericTest.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Anyhow ... what is happening is that when you then do this:
ll.aa[0] = 6;
the JVM is trying to cast the ll.aa
to an Integer[]
... because that is what the static typing says that it should be. But it isn't an Integer[]
. It is an Object[]
. Since Object[]
is not assignment compatible with an Integer[]
that gives you a class cast exception.
(Why is it doing a hidden type cast? Well this is how the JVM ensures runtime type safety in the face of possible unsafe casts and the like!)
How to fix it?
Avoid using T[]
. Use List<T>
instead.
Unfortunately, if you have to use T[]
there is no easy fix. Basically arrays of a generic type parameter are difficult to create. You end up having to pass the Class
object for the parameter's actual class as an extra parameter. Something like this:
import java.lang.reflect.Array;
public class GenericTest<T> {
T [] aa;
T bb;
public GenericTest(Class<T> cls, T x, T y) {sy
aa = (T[]) Array.newInstance(cls, 2);
aa[0] = x; aa[1] = y;
System.out.println(aa[0] + " " + aa[1]); //OK
}
static public void main(String[] args) {
GenericTest<Integer> ll = new GenericTest<>(Integer.class, 1, 2);
ll.bb = 1; // OK
ll.aa[0] = 6; // ClassCastException from Object to Integer
}
}
There is still a warning about an unsafe typecast ... but in this case it is safe to suppress the warning.
For Java 8 onwards, there is another solution which involves passing a reference to the array constructor for Integer[]
; see Andy Turner's answer. This is cleaner than using reflection and calling Array.newInstance
, but you still have to pass an extra parameter to the constructor.
This is what happens when you use generics. Because generics are erased at runtime, compiler still needs to somehow be safe (after erasure) that things work correctly. Let's simplify this:
GenericTest<Integer> ll = new GenericTest<>(1,2);
ll.bb = 1; // OK
System.out.println(ll.aa.getClass());
The last line is going to be translated to:
28: getfield #7 // Field aa:[Ljava/lang/Object;
31: checkcast #42 // class "[Ljava/lang/Integer;"
notice the checkcast
. Since your T
was resolved as Integer
, means that the array must be Integer[]
too; when in reality it is Object []
. Compiler is trying to warn you btw when you do :
T [] aa = (T []) new Object[2];
because this is unsafe. In general, generic arrays are a major headache in java, imo.
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