4

我的代码中出现以下异常:线程“main”中的异常 java.lang.ClassCastException: [Ljava.lang.Comparable; 不能转换为 [LElement; 在以下电话中:

Element<K,V>[] heap = (Element<K,V>[]) new Comparable[size]; 

其中 Element 定义如下:

class Element<K, V> implements Comparable<Element<K, V>>{
    long timeStamp;
    K key;
    V val;
    @Override
    public int compareTo(Element<K, V> o) {
    return new Long(timeStamp).compareTo(o.timeStamp);
}
Element(long ts, K key, V val){
    this.timeStamp = ts;
    this.key = key;
    this.val = val;
    }

}

任何帮助是极大的赞赏!

4

3 回答 3

4

发生这种情况是因为 Java 类型擦除。要回答这个问题,我需要解释无界通配符、有界通配符和类型擦除。如果您熟悉它,请随意跳过任何部分。

这篇文章的内容是从 java 文档中组装而成的。

1. 无界通配符

无界通配符类型使用通配符 ( ?) 指定,例如List<?>。这称为未知类型列表。在两种情况下,无界通配符是一种有用的方法:

  • 如果您正在编写可以使用 Object 类中提供的功能实现的方法。

  • 当代码使用泛型类中不依赖于类型参数的方法时。例如,List.sizeList.clear。事实上,Class<?>之所以经常使用,是因为其中的大多数方法Class<T>都不依赖于T.

2. 有界通配符

考虑一个可以绘制矩形和圆形等形状的简单绘图应用程序。要在程序中表示这些形状,您可以定义一个类层次结构,如下所示:

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

这些类可以在画布上绘制:

public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
   }
}

任何绘图通常都会包含许多形状。假设它们被表示为一个列表,那么在 Canvas 中有一个方法来绘制它们会很方便:

public void drawAll(List<Shape> shapes) {
    for (Shape s: shapes) {
        s.draw(this);
   }
}

现在,类型规则说drawAll()只能在完全为 Shape 的列表上调用:例如,它不能在 a 上调​​用List<Circle>。这是不幸的,因为该方法所做的只是从列表中读取形状,所以它也可以在List<Circle>. 我们真正想要的是该方法接受任何形状的列表:

public void drawAll(List<? extends Shape> shapes) {
    ...
}

这里有一个很小但非常重要的区别:我们将类型替换List<Shape>List<? extends Shape>. 现在drawAll()将接受 的任何子类的列表Shape,因此我们现在可以根据需要在 a 上调​​用它List<Circle>

List<? extends Shape>是有界通配符的一个示例。代表未知类型,但是,在这种?情况下,我们知道这种未知类型实际上是 Shape 的子类型。(注意:它可以是 Shape 本身,也可以是某个子类;它不需要从字面上扩展 Shape。)我们说 Shape 是通配符的上限

类似地,作为有界通配符的语法? super T表示未知类型,它是 T 的超类型。例如,ArrayedHeap280 包​​括ArrayedHeap280<Integer>ArrayedHeap280<Number>ArrayedHeap280<Object>。正如您在Integer 类的 java 文档中看到的那样,Integer 是 Number 的子类,而 Number 又是 Object 的子类。

类整数 * java.lang.Object * java.lang.Number * java.lang.Integer

3.类型擦除和ClassCastException

泛型被引入 Java 语言以在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java 编译器将类型擦除应用于:

  • 如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或 Object。因此,生成的字节码只包含普通的类、接口和方法。
  • 必要时插入类型转换以保持类型安全。
  • 生成桥方法以保留扩展泛型类型中的多态性。

在类型擦除过程中,Java 编译器擦除所有类型参数,如果类型参数是有界的,则将每个类型参数替换为其第一个边界,如果类型参数是无界的,则将其替换为 Object。

考虑以下表示单链表中节点的泛型类:

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) }
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
```
>Because the type parameter T is unbounded, the Java compiler replaces it with Object:
```java
public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

在以下示例中,通用 Node 类使用有界类型参数:

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

Java 编译器将有界类型参数 T 替换为第一个绑定类 Comparable:

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}
```
> Sometimes type erasure causes a situation that you may not have anticipated. The following example shows how this can occur. 
> 
> Given the following two classes:
```java
public class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

类型擦除后,NodeandMyNode类变为:

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

考虑以下代码:

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     
Integer x = mn.data;    // Causes a ClassCastException to be thrown.

类型擦除后,此代码变为:

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.

以下是代码执行时发生的情况:

  • n.setData("Hello");导致方法setData(Object)在 class 的对象上执行MyNode。(MyNode继承setData(Object)自的类Node。)
  • 在的体内setData(Object)

data引用的对象的字段n分配给 a String

  • data通过 引用的同一对象的字段mn可以被访问,并且应该是一个Integer(因为mn是 aMyNode这是一个Node<Integer>.尝试将 a 分配String给 anInteger会导致ClassCastException来自 Java 编译器在分配时插入的强制转换。
于 2017-01-30T04:25:43.340 回答
3

这不是多态性的工作方式。您不能通过子类引用来引用超类(或接口)“对象”。但是,您可以通过其实现接口或任何超类的名称来引用任何子类对象。

Comparable c = new Element();

或者一般来说,您可以记住这始终是合法的:

Object c = new String();

但这永远不行:

AnyClass m = new Object();
于 2013-08-17T18:58:18.543 回答
2

数组不能以与类相同的多态方式进行转换。考虑这段代码:

Comparable[] foo = new Comparable[size];
foo[0] = Long.valueOf(123L);
Element<K,V>[] heap = (Element<K,V>[]) foo;

Element<K,V> thisFails = heap[0];    // this isn't safe!

自然,这段代码没有意义。您会将 Long 放入您的元素堆中,这是不对的。违反直觉的事情是反过来也不起作用:

Element<K,V>[] heap = new Element<K,V>[];
Comparable[] foo = (Comparable[]) heap;
foo[0] = Long.valueOf(123L);
// ...which also sets heap[0], because they're two references to the same
// array object. Unlike C-style languages, arrays are objects in Java.

Element<K,V> thisFails = heap[0];    // this isn't safe!

这样做的结果是数组不能在任何一个方向上投射extends(泛型可以,但是有关于and的具体而神秘的规则super;那是另一回事。)

于 2013-08-17T20:25:03.817 回答