Java 重写超级';s法(可比<;T>;)

Java 重写超级';s法(可比<;T>;),java,generics,comparable,Java,Generics,Comparable,我确实搜索过,但没有找到类似的问题。如果这是重复的,我很抱歉。我编写了一个常规队列方法,并尝试将其扩展为具有优先级队列。我不明白为什么我只能使用super类的方法插入,而不能插入子类中的代码,而存储[n]是可比较的,数据也是可比较的。如果我在子类中尝试这样做,将抛出ClassCastException。我做错什么了吗 RegularQueue.java import java.util.Arrays; public class RegularQueue<T> { prot

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

}
导入java.util.array;
公共类正则队列{
受保护的内部容量;
受保护的T[]存储;
@抑制警告(“未选中”)
常规队列(整数容量){
这个。容量=容量;
存储=(T[])新对象[this.capacity];
}
@凌驾
公共字符串toString(){
返回“队列{”+
“容量=”+容量+
“,storage=“+Arrays.toString(存储)+
'}';
}
无效插入(T数据){
存储器[0]=数据;
}
}
PriorityQueue.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;
    }
}
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());
    }

}
公共类优先级队列扩展了RegularQueue{
优先级队列(整数容量){
超级(容量);
}
//这不管用
@凌驾
空白插入(可比数据){
存储器[1]=数据;
}
//--这个很好用。
//@覆盖
//空白插入(可比数据){
//超级.插入(数据);
//    }
公共静态void main(字符串[]args){
PriorityQueue q=新的PriorityQueue(5);
q、 插入(1);
System.out.println(q.toString());
}
}

您看到这个ClassCastExpression是因为在RegularQueue中您正在使用非类型安全赋值
存储=(T[])新对象[this.capacity]
。在PriorityQueue中,您使用
Comparable
作为RegularQueue的
T
的类型参数。因此,在编译时已知,此
T
在运行时必须是
可比的
或其子类型。因此,每次您访问
T[]存储时,编译器都会在PriorityQueue内发出
Comparable[]
强制转换以强制执行此操作。
现在的问题是,
storage
实际上不是类型
T[]
,而是类型
Object[]
,这导致了您看到的ClassCastException。以任何方式访问字段时都会发生这种情况,甚至
storage.length
会触发它

在调用
super.insert
insert
方法中没有看到此异常的原因是它没有直接访问
存储
。只有超级实现会执行此操作,但不会执行任何强制转换,因为在RegularQueue内部,
T
的类型在编译时是未知的

解决方案是不要将存储声明为
T[]
,而是使用
Object[]
,因为这是实际的类型

其他人向JDK团队报告了这个bug,但报告(如预期的那样)已被解决为“不是问题”。然而,JDK开发人员之一斯图尔特·马克斯(Stuart Marks)在报告中深入解释了根本问题(可能比这个答案更好)。我强烈建议您阅读。

此答案将尝试补充马可诺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
  ...
// 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
    中,而不存在
    regularlqueue
  • 它根据
    存储
    检查
    java.lang.Comparable[]
    ,因为
    Comparable
    的擦除是
    Comparable
    ,所以
    存储
    优先级队列
    视图中属于
    Comparable[]
    类型
  • 另外
    ClassCastException
    也将抛出

    • PriorityQueue扩展了RegularQueue
    • PriorityQueue扩展了RegularQueue
    当type参数的type/erase为
    Object
    时,
    ClassCastException
    不会抛出(
    checkcast
    将消失)

    • PriorityQueue扩展了RegularQueue
    • PriorityQueue扩展了RegularQueue
    解决方案 正如马可诺1234所说

    解决方案是不将存储声明为T[],而是使用对象[],因为它是实际类型

    为了更好的类型安全性和可读性,我建议将
    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];
        }
    ...
    
    公共类优先级队列扩展了RegularQueue{
    ...
    @凌驾
    空白插入(可比数据){
    设置存储(1,新对象());//编译错误
    //如果存储受到保护,则允许执行以下操作,仅当将值强制转换为可比值时才会发生错误
    //存储器[1]=新对象();
    }
    公共可比getByIndex(int索引){
    返回getStorage(索引);
    //使用存储值时需要重复强制转换
    //返回(可比)存储[索引];
    }
    ...
    
    参考资料:


    问题是,编译器实际上不需要将
    存储
    转换为
    可比[]
    ,因为
    存储
    的唯一用途是设置它的一个元素,这可以在
    对象[]上完成
    。编译器选择在
    优先级队列
    中使用
    存储时插入强制转换,即使我们不使用
    存储
    T[]
    这一事实,并且此编译器决定是可接受的,但不是强制性的。可能其他编译器不会在此处插入强制转换。