java—重写super方法时的classcastexception(类似< t>)

ubby3x7f  于 2021-07-05  发布在  Java
关注(0)|答案(2)|浏览(379)

我确实搜索过,但没有找到类似的问题。如果这是重复的,我很抱歉。我编写了一个常规队列方法,并尝试将其扩展为具有优先级队列。我不明白为什么我只能插入超类的方法,而不能插入子类中的代码,而存储[n]是可比较的,数据也是可比较的。如果我在子类中尝试这样做,将抛出classcastexception。我做错什么了吗?
正则队列.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;
    }
}

优先级队列.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());
    }

}
9o685dep

9o685dep1#

您看到这个classcastexpression是因为在regularqueue中使用的是非类型安全的赋值 storage = (T[]) new Object[this.capacity] . 在您使用的priorityqueue中 Comparable<...> 作为的类型参数 T 常规队列。因此,在编译时已知 T 必须在运行时 Comparable 或者它的一个子类型。因此编译器发出 Comparable[] 每次访问时在priorityqueue中强制转换 T[] storage 来执行这个。
现在的问题是 storage 不是那种类型的 T[] 但只是一种类型 Object[] 这导致了你看到的classcastexception。以任何方式访问字段时都会发生这种情况,甚至 storage.length 触发它。
为什么你没有看到这个例外 insert 方法调用 super.insert 它不直接访问 storage . 只有超级实现会这样做,但是它不会执行任何强制转换,因为在regularqueue内部 T 在编译时未知。
解决办法是不申报 storage 作为 T[] 而是使用 Object[] 因为那是实际的类型。
其他人向jdk团队报告了这个bug,但是这个报告(正如预期的那样)被解决为“不是问题”。然而,jdk开发人员之一stuartmarks在对报告的评论中深入解释了根本问题(可能比这个答案更好)。我强烈建议你读一读。

sg24os4d

sg24os4d2#

这个答案将试图补充马可诺1234的答案。

使用eclipse类文件编辑器,我们可以在类文件中看到 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 它被检查为 java.lang.Comparable[] 反对 storage 作为擦除 Comparable<T>Comparable ,所以 storage 属于类型 Comparable[] 在…看来 PriorityQueue .
此外 ClassCastException 也会扔,因为 PriorityQueue<T> extends RegularQueue<Number> PriorityQueue extends RegularQueue ClassCastException 不会扔( checkcast 将消失),当类型参数的类型/擦除 Object .
PriorityQueue extends RegularQueue PriorityQueue<T> extends RegularQueue<Object> ###解决方案
正如马可诺1234所说,
解决方案是不将存储声明为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()); // 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];
    }
...

参考文献:
参考型铸造
java虚拟机指令集-checkcast

相关问题