Reputation: 1166
I did search but couldn't find a similar problem. I'm sorry if this is duplicated. I write a regular queue method and try to extends it to have a priority queue. I don't understand why I can only insert if I use the super class' method, and not the code in the sub class while storage[n] is a Comparable and data is a Comparable too. If I try to do that in the sub class, a ClassCastException will be thrown. Did I do anything wrong?
RegularQueue.java
import java.util.Arrays;
public class RegularQueue<T> {
protected int capacity;
protected T[] storage;
@SuppressWarnings("unchecked")
RegularQueue(int capacity) {
this.capacity = capacity;
storage = (T[]) new Object[this.capacity];
}
@Override
public String toString() {
return "Queue{" +
"capacity=" + capacity +
", storage=" + Arrays.toString(storage) +
'}';
}
void insert(T data) {
storage[0] = data;
}
}
PriorityQueue.java
public class PriorityQueue<T> extends RegularQueue<Comparable<T>> {
PriorityQueue(int capacity) {
super(capacity);
}
// This doesn't work
@Override
void insert(Comparable<T> data) {
storage[1] = data;
}
// ---> This works fine.
// @Override
// void insert(Comparable<T> data) {
// super.insert(data);
// }
public static void main(String[] args) {
PriorityQueue<Integer> q = new PriorityQueue<>(5);
q.insert(1);
System.out.println(q.toString());
}
}
Upvotes: 3
Views: 162
Reputation: 8114
Using Eclipse Class File Editor, we can see in the class file
RegularQueue.class
// Signature: <T:Ljava/lang/Object;>Ljava/lang/Object;
public class RegularQueue {
...
// Field descriptor #8 [Ljava/lang/Object;
// Signature: [TT;
protected java.lang.Object[] storage;
...
// Method descriptor #56 (Ljava/lang/Object;)V
// Signature: (TT;)V
// Stack: 3, Locals: 2
void insert(java.lang.Object data);
0 aload_0 [this]
1 getfield RegularQueue.storage : java.lang.Object[] [19]
4 iconst_0
5 aload_1 [data]
6 aastore
7 return
...
PriorityQueue.class
// Signature: <T:Ljava/lang/Object;>LRegularQueue<Ljava/lang/Comparable<TT;>;>;
public class PriorityQueue extends RegularQueue {
...
// Method descriptor #19 (Ljava/lang/Comparable;)V
// Signature: (Ljava/lang/Comparable<TT;>;)V
// Stack: 3, Locals: 2
void insert(java.lang.Comparable data);
0 aload_0 [this]
1 getfield PriorityQueue.storage : java.lang.Object[] [22]
4 checkcast java.lang.Comparable[] [26] //<-- reason for ClassCastException
7 iconst_1
8 aload_1 [data]
9 aastore
...
checkcast
only exists in PriorityQueue
, and not RegularQueue
java.lang.Comparable[]
against storage
as erasure of Comparable<T>
is Comparable
, so storage
is of type Comparable[]
in the view of PriorityQueue
.In addition
ClassCastException
will also throw, for
PriorityQueue<T> extends RegularQueue<Number>
PriorityQueue<T> extends RegularQueue<String>
ClassCastException
will not throw (checkcast
will disappear), when the type/erasure of the type argument is Object
.
PriorityQueue<T> extends RegularQueue<T>
PriorityQueue<T> extends RegularQueue<Object>
As suggested by Marcono1234,
The solution is to not declare storage as T[] but instead use Object[] since that is the actual type.
For better type safety and readability, I suggest to make storage
as private field also, and provide setStorage
and getStorage
method:
protected void setStorage(int index, T data) {
storage[index] = data;
}
@SuppressWarnings("unchecked")
protected T getStorage(int index) {
return (T) storage[index];
}
As we can see in following example,
public class PriorityQueue<T> extends RegularQueue<Comparable<T>> {
...
@Override
void insert(Comparable<T> data) {
setStorage(1, new Object()); // Compile error
// following is allowed if storage is protected, error only occur when casting the value to Comparable<T>
// storage[1] = new Object();
}
public Comparable<T> getByIndex(int index) {
return getStorage(index);
// Need to repeatedly cast when using storage value
// return (Comparable<T>) storage[index];
}
...
Reference:
Reference Type Casting
The Java Virtual Machine Instruction Set - checkcast
Upvotes: 0
Reputation: 6914
You are seeing this ClassCastExpression because in RegularQueue you are using the non type-safe assignment storage = (T[]) new Object[this.capacity]
. In PriorityQueue you are using Comparable<...>
as type argument for T
of RegularQueue. It is therefore known at compile time that this T
must at runtime be Comparable
or a subtype of it. Thus the compiler emits Comparable[]
casts inside PriorityQueue every time you access T[] storage
to enforce this.
The issue is now that storage
is not actually of type T[]
but only of type Object[]
which is causing the ClassCastException you are seeing. This occurs when accessing the field in any way, even storage.length
triggers it.
The reason why you are not seeing this exception in the insert
method calling super.insert
is that it does not directly access storage
. Only the super implementation does this which however does not perform any casts since inside of RegularQueue the type of T
is unknown at compile time.
The solution is to not declare storage
as T[]
but instead use Object[]
since that is the actual type.
Someone else reported this as bug to the JDK team but the report has (as expected) been resolved as "Not an Issue". However Stuart Marks, one of the JDK developers, explains in his comment on the report in depth (and probably better than this answer) the underlying issue. I highly recommend reading it.
Upvotes: 3