重写超级方法时的 ClassCastException (Comparable<T>)
ClassCastException when overriding a super's method (Comparable<T>)
我搜索了一下,没有找到类似的问题。如果这是重复的,我很抱歉。我编写了一个常规队列方法并尝试将其扩展为具有优先级队列。我不明白为什么我只能在使用 super class' 方法时插入,而不是 sub class 中的代码,而 storage[n] 是 Comparable 并且 data 也是 Comparable。如果我尝试在子 class 中这样做,将抛出 ClassCastException。我做错了什么吗?
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());
}
}
您看到此 ClassCastExpression 是因为在 RegularQueue 中您使用的是非 type-safe 赋值 storage = (T[]) new Object[this.capacity]
。在 PriorityQueue 中,您使用 Comparable<...>
作为 RegularQueue T
的类型参数。因此在编译时已知此 T
在运行时必须是 Comparable
或其子类型。因此,每次您访问 T[] storage
时,编译器都会在 PriorityQueue 中发出 Comparable[]
强制转换。
现在的问题是 storage
实际上不是 T[]
类型,而只是 Object[]
类型,这导致了您看到的 ClassCastException。以任何方式访问该字段时都会发生这种情况,即使 storage.length
也会触发它。
之所以在调用 super.insert
的 insert
方法中没有看到此异常,是因为它没有直接访问 storage
。只有超级实现会执行此操作,但不会执行任何转换,因为在 RegularQueue 内部,T
的类型在编译时是未知的。
解决方案是不将 storage
声明为 T[]
,而是使用 Object[]
,因为这是实际类型。
其他人将此作为错误报告给 JDK 团队,但报告已(如预期)作为“不是问题”得到解决。然而,JDK 开发人员之一 Stuart Marks 在报告的 his comment 中深入(可能比这个答案更好)解释了潜在问题。我强烈推荐阅读它。
本回答会尽量补充Marcono1234的回答。
使用Eclipse Class文件编辑器,我们可以在class文件中看到
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
仅存在于 PriorityQueue
,而不存在于 RegularQueue
- 根据
storage
检查 java.lang.Comparable[]
,因为 Comparable<T>
的擦除是 Comparable
,所以 storage
的类型是 Comparable[]
PriorityQueue
. 的观点
此外
ClassCastException
也会抛出,因为
PriorityQueue<T> extends RegularQueue<Number>
PriorityQueue<T> extends RegularQueue<String>
ClassCastException
不会抛出(checkcast
会消失),当类型参数的type/erasure为Object
.
时
PriorityQueue<T> extends RegularQueue<T>
PriorityQueue<T> extends RegularQueue<Object>
解决方案
根据 Marcono1234 的建议,
The solution is to not declare storage as T[] but instead use Object[] since that is the actual type.
为了更好的类型安全性和可读性,我建议将storage
也设为私有字段,并提供setStorage
和getStorage
方法:
protected void setStorage(int index, T data) {
storage[index] = data;
}
@SuppressWarnings("unchecked")
protected T getStorage(int index) {
return (T) storage[index];
}
正如我们在下面的例子中看到的,
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 Type Casting
The Java Virtual Machine Instruction Set - checkcast
我搜索了一下,没有找到类似的问题。如果这是重复的,我很抱歉。我编写了一个常规队列方法并尝试将其扩展为具有优先级队列。我不明白为什么我只能在使用 super class' 方法时插入,而不是 sub class 中的代码,而 storage[n] 是 Comparable 并且 data 也是 Comparable。如果我尝试在子 class 中这样做,将抛出 ClassCastException。我做错了什么吗?
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());
}
}
您看到此 ClassCastExpression 是因为在 RegularQueue 中您使用的是非 type-safe 赋值 storage = (T[]) new Object[this.capacity]
。在 PriorityQueue 中,您使用 Comparable<...>
作为 RegularQueue T
的类型参数。因此在编译时已知此 T
在运行时必须是 Comparable
或其子类型。因此,每次您访问 T[] storage
时,编译器都会在 PriorityQueue 中发出 Comparable[]
强制转换。
现在的问题是 storage
实际上不是 T[]
类型,而只是 Object[]
类型,这导致了您看到的 ClassCastException。以任何方式访问该字段时都会发生这种情况,即使 storage.length
也会触发它。
之所以在调用 super.insert
的 insert
方法中没有看到此异常,是因为它没有直接访问 storage
。只有超级实现会执行此操作,但不会执行任何转换,因为在 RegularQueue 内部,T
的类型在编译时是未知的。
解决方案是不将 storage
声明为 T[]
,而是使用 Object[]
,因为这是实际类型。
其他人将此作为错误报告给 JDK 团队,但报告已(如预期)作为“不是问题”得到解决。然而,JDK 开发人员之一 Stuart Marks 在报告的 his comment 中深入(可能比这个答案更好)解释了潜在问题。我强烈推荐阅读它。
本回答会尽量补充Marcono1234的回答。
使用Eclipse Class文件编辑器,我们可以在class文件中看到
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
仅存在于PriorityQueue
,而不存在于RegularQueue
- 根据
storage
检查java.lang.Comparable[]
,因为Comparable<T>
的擦除是Comparable
,所以storage
的类型是Comparable[]
PriorityQueue
. 的观点
此外
ClassCastException
也会抛出,因为
PriorityQueue<T> extends RegularQueue<Number>
PriorityQueue<T> extends RegularQueue<String>
ClassCastException
不会抛出(checkcast
会消失),当类型参数的type/erasure为Object
.
PriorityQueue<T> extends RegularQueue<T>
PriorityQueue<T> extends RegularQueue<Object>
解决方案
根据 Marcono1234 的建议,
The solution is to not declare storage as T[] but instead use Object[] since that is the actual type.
为了更好的类型安全性和可读性,我建议将storage
也设为私有字段,并提供setStorage
和getStorage
方法:
protected void setStorage(int index, T data) {
storage[index] = data;
}
@SuppressWarnings("unchecked")
protected T getStorage(int index) {
return (T) storage[index];
}
正如我们在下面的例子中看到的,
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 Type Casting
The Java Virtual Machine Instruction Set - checkcast