"java.lang.ClassCastException: [Ljava.lang.Comparable;" 无法转换为类型

5

我的代码出现了以下异常: Exception in thread "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;
    }

非常感谢您的帮助!


你能展示一下你在哪里使用那段代码吗?那段代码本身并没有意义。编译器应该已经给出了一个“未经检查的转换”警告。不过,我们还需要一些周围的代码。 - Rohit Jain
我正在尝试创建一个Element<K, V>数组。我知道ArrayList可以工作,但在这里我需要像Element<K, V>[]这样简单的东西,而Java并不直接支持它。我也知道类似以下的语句是被允许的:heap = (K[]) new Comparable[capacity]; - user2692465
1
你尝试过 Element<K, V>[] heap = (Element<K, V>[]) new Element[size]; 吗? - sgbj
3个回答

5
这是由于Java类型擦除造成的。要回答这个问题,我需要解释无界通配符、有界通配符和类型擦除。如果您对此已经熟悉,可以随意跳过任何部分。
本文内容来自Java文档。
1. 无界通配符
无界通配符类型使用通配符字符(?)指定,例如List<?>。这被称为未知类型的列表。有两种情况下,无界通配符是一种有用的方法:
- 如果您正在编写一个可以使用Object类提供的功能实现的方法。 - 当代码在使用不依赖于类型参数的泛型类中的方法时。例如,List.size或List.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的列表上调用:例如,它不能在List<Circle>上调用。这很不幸,因为该方法只是从列表中读取形状,所以它同样可以在List<Circle>上调用。我们真正想要的是该方法接受任何类型的形状列表。
public void drawAll(List<? extends Shape> shapes) {
    ...
}

这里有一个很小但非常重要的区别:我们将类型 List<Shape> 替换为 List<? extends Shape>。现在,drawAll() 将接受任何 Shape 子类的列表,因此我们现在可以在 List<Circle> 上调用它,如果需要的话。 List<? extends Shape> 是有界通配符的一个示例。 ? 代表未知类型,但在这种情况下,我们知道这个未知类型实际上是 Shape 的子类型。 (注意:它可以是 Shape 本身,或者一些子类;它不一定是 Shape 的直接子类。)我们说 Shape 是通配符的上限。
同样地,语法? super T表示有一个未知类型,它是T的超类型,这被称为有界通配符。 例如,ArrayedHeap280包含ArrayedHeap280<Integer>ArrayedHeap280<Number>ArrayedHeap280<Object>。 正如你在java documentation for Integer class中看到的那样,Integer是Number的子类,而Number又是Object的子类。

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);
    }
}

在类型擦除后,NodeMyNode 类变为:
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") 导致对类MyNode的对象执行setData(Object)方法。(MyNode类从Node继承了setData(Object)方法。) - 在setData(Object)方法的方法体中, - 引用n所指向的对象的data字段被赋值为一个字符串。
以下是更多内容: - 相同对象(通过mn引用),也可以访问该对象的data字段,该字段应该是一个整数(因为mn是个MyNode,它是一个Node)。试图给Integer类型的变量赋一个字符串会导致Java编译器在赋值时插入一个转换,最终导致ClassCastException。

3

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

Comparable c = new Element();

在一般情况下,您可以记住这个总是合法的:
Object c = new String();

但这是绝不可以接受的:
AnyClass m = new Object();

我理解这个。然而,我试图将引用转换为可比较/对象数组的子类。我认为这是人们使用泛型创建对象/可比较数组的典型技巧 E[] arr = E[] new Object[size]; E[] carr = E[] new Comparable[size]; - user2692465
在这种情况下,Element<K, V> 的擦除是 Element,这意味着您可以使用 new Element[size](并获得未经检查的转换警告)。 - Jonathan Callen

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!

这样做的结果是,数组不能在任何方向上被转换。(泛型可以,但需要特定而深奥的关于extendssuper的规则;那是另一回事。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接