英文:
ClassCastException when overriding a super's method (Comparable<T>)
问题
我确实进行了搜索,但找不到类似的问题。如果这是重复的问题,我很抱歉。我编写了一个常规的队列方法,并尝试将其扩展为具有优先级的队列。我不明白为什么我只能在使用超类的方法时才能插入,而在子类中的代码却不行,尽管 storage[n] 是 Comparable
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);
}
// 这种写法不起作用
@Override
void insert(Comparable<T> data) {
storage[1] = data;
}
// ---> 这种写法可以正常工作。
// @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());
}
}
以上为你提供的代码的翻译。
英文:
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<T> 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());
}
}
答案1
得分: 3
你看到这个ClassCastExpression是因为在RegularQueue中,你使用了非类型安全赋值storage = (T[]) new Object[this.capacity]
。在PriorityQueue中,你将Comparable<...>
用作RegularQueue的T
的类型参数。因此在编译时已知,这个T
在运行时必须是Comparable
或其子类型。因此编译器会在每次访问T[] storage
时发出Comparable[]
的强制转换,以执行此操作。
问题在于,storage
实际上并不是T[]
类型,而只是Object[]
类型,这导致了你所看到的ClassCastException。无论以任何方式访问该字段,甚至是storage.length
,都会触发这个异常。
你没有在调用super.insert
的insert
方法中看到这个异常的原因是它并没有直接访问storage
。只有超类的实现才会这样做,然而在RegularQueue内部,在编译时不知道T
的类型,因此不执行任何强制转换。
解决方案是不将storage
声明为T[]
,而是使用实际的类型Object[]
。
另有其他人将此问题报告给了JDK团队,但报告已被(预料中的)解决为“不是问题”。然而,JDK开发人员之一Stuart Marks在该报告的评论中深入解释了底层问题(可能比这个回答更好)。我强烈建议阅读他的评论。链接。
英文:
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.
答案2
得分: 0
本答案将尝试补充Marcono1234的回答。##
使用Eclipse Class File Editor,我们可以在类文件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] //<-- 出现ClassCastException的原因
7 iconst_1
8 aload_1 [data]
9 aastore
...
checkcast
只存在于PriorityQueue
中,而不在RegularQueue
中。checkcast
检查的是storage
是否为java.lang.Comparable[]
,因为Comparable<T>
的类型擦除后是Comparable
,所以在PriorityQueue
的视图中,storage
的类型是Comparable[]
。
此外,当类型参数的类型擦除为Object
时,也会抛出ClassCastException
:
PriorityQueue<T> extends RegularQueue<Number>
PriorityQueue<T> extends RegularQueue<String>
当类型参数的类型擦除为Object
时,不会抛出ClassCastException
(checkcast
将消失):
PriorityQueue<T> extends RegularQueue<T>
PriorityQueue<T> extends RegularQueue<Object>
解决方案
如Marcono1234建议的那样,
解决方案是不要将storage声明为T[],而是使用Object[],因为那才是实际类型。
为了获得更好的类型安全性和可读性,建议将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()); // 编译错误
// 如果storage是受保护的,以下内容是允许的,只有在将值强制转换为Comparable<T>时才会出错
// storage[1] = new Object();
}
public Comparable<T> getByIndex(int index) {
return getStorage(index);
// 使用storage值时需要反复强制转换
// return (Comparable<T>) storage[index];
}
...
参考链接:
Reference Type Casting
The Java Virtual Machine Instruction Set - checkcast
英文:
This answer will try to supplement on Marcono1234 answer.
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 inPriorityQueue
, and notRegularQueue
- It is checked for
java.lang.Comparable[]
againststorage
as erasure ofComparable<T>
isComparable
, sostorage
is of typeComparable[]
in the view ofPriorityQueue
.
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>
Solution
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论