重写超级方法时的 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.insertinsert 方法中没有看到此异常,是因为它没有直接访问 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
  ...
  1. checkcast 仅存在于 PriorityQueue,而不存在于 RegularQueue
  2. 根据 storage 检查 java.lang.Comparable[],因为 Comparable<T> 的擦除是 Comparable,所以 storage 的类型是 Comparable[] PriorityQueue.
  3. 的观点

此外 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也设为私有字段,并提供setStoragegetStorage方法:

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