Reputation: 21
I just start learning Generics of Java. It confused me when I try to read some sample examples, just as below. I know it's something related to the type erasure, but I can not figure out what's going wrong.
package graph;
import java.util.*;
public class JustTest<T> {
public static void main (String[] args) {
ArrayQueue<String> q = new ArrayQueue<String>();
q.offer(new String("string_1"));
q.offer(new String("string_2"));
Object[] ob = q.getArray();
System.out.println(ob);
// The lines below went wrong when running
// Error information:
//java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
String[] str = q.getArray();
System.out.println(str);
}
}
class ArrayQueue<T> {
private static final int MINIMUM_SIZE = 1024;
private T[] array = (T[]) new Object[MINIMUM_SIZE];
private int lastIndex = 0;
public T[] getArray () {
return array;
}
public T getElement () {
return array[0];
}
public boolean offer(T value) {
array[lastIndex % array.length] = value;
lastIndex++;
return true;
}
}
Upvotes: 2
Views: 104
Reputation: 16224
Update
I missed up something last time, if you are learning generics, just picked up a little twisted example, you probably need to start with some classes like Pair, but well.
I Made this changes to your ArrayQueue class:
The @SuppressWarnings is added to avoid the error casting, because we know that the cast we made here manually is right, so, suppress this warning is fine in this case.
I also added two constructors, one parameter and two parameters, the first parameter is the Class<T> c
is the type you will say it will have in the moment you create it, and the second one is if you want another sizes for your array.
in this line:
array = (T[]) Array.newInstance(c, MINIMUM_SIZE);
is how I made possible to create the generic array, with the Array.newInstance method, you can tell the class and the size, and the type is given as parameter, that's the trick.
@SuppressWarnings("unchecked")
class ArrayQueue<T> {
private static final int MINIMUM_SIZE = 1024;
private T[] array;
private int lastIndex = 0;
public ArrayQueue(Class<T> c){
array = (T[]) Array.newInstance(c, MINIMUM_SIZE);
}
public ArrayQueue(Class<T> c, int size){
array = (T[]) Array.newInstance(c, size);
}
public T[] getArray () {
return array;
}
public T getElement () {
return array[0];
}
public boolean offer(T value) {
array[lastIndex % array.length] = value;
lastIndex++;
return true;
}
}
And in the main class, for testing it:
public static void main(String[] args)
{
ArrayQueue<String> q = new ArrayQueue<String>(String.class);
q.offer(new String("string_1"));
q.offer(new String("string_2"));
String[] ob = q.getArray();
System.out.println(ob[0]);
System.out.println(ob[1]);
// The lines below went wrong when running
// Error information:
//java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
String[] str = q.getArray();
System.out.println(str[0]);
}
Upvotes: 1
Reputation: 33845
The problem lies with the unchecked cast in your ArrayQueue
class:
private T[] array = (T[]) new Object[MINIMUM_SIZE];
You basically say here that an Object[]
is actually a T[]
, but this cast is 'unchecked' so there is never a check at runtime. If there was, this cast would fail, since an Object[]
is not a T[]
unless T
is Object
. But since the cast is unchecked, the program doesn't crash on this line.
The program crashes on the line where the compiler inserts an implicit cast, at the point where T
is a known concrete type:
String[] str = q.getArray();
Here you go from T[]
to String[]
, so the compiler will insert an implicit cast here:
String[] str = (String[]) q.getArray();
And this is where it blows up, since your earlier cast to T[]
was faulty.
With the interface you currently have, the most convenient solution would be to pass a reference to a constructor of T[]
, so you can actually create a T[]
:
class ArrayQueue<T> {
private static final int MINIMUM_SIZE = 1024;
private T[] array;
...
public ArrayQueue(IntFunction<T[]> arrayCons) {
array = arrayCons.apply(MINIMUM_SIZE);
}
...
ArrayQueue<String> q = new ArrayQueue<>(String[]::new); // passing a constructor reference
Or use a similar solution to what API Collection
s do, using an Object[]
instead of a T[]
array, and require a reference to a T[]
constructor when calling getArray
:
class ArrayQueue<T> {
private static final int MINIMUM_SIZE = 1024;
private Object[] array = new Object[MINIMUM_SIZE];
...
public T[] getArray(IntFunction<T[]> arrayCons) {
T[] ret = arrayCons.apply(size()); // implement size
System.arrayCopy(array, firstIndex, ret, 0, size());
return ret;
}
public T getElement() {
return (T) array[0]; // a safe unchecked cast, since you only put Ts into the array.
}
}
Upvotes: 1
Reputation: 4122
During runtime, T
s are replaced by Object
, unless there is a bounding type. This means that
private T[] array = (T[]) new Object[MINIMUM_SIZE];
actually becomes
private Object[] array = new Object[MINIMUM_SIZE];
So when you call getArray
, you get an array of Object
, which can't be assigned a reference of type String[]
. Hence the ClassCastException
.
Each element in the array can, by itself, be cast from Object
to String
(specifically because it's String
), but the array as a whole can't be cast.
Which is why you get
[Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
The [L
means it's an array.
To get them as strings, you can do this thing:
String[] str = Arrays.toList(q.getArray()).stream().map(Object::toString()).collect(Collectors.toList()).toArray();
Which should work.
Upvotes: 0