我的代码中出现以下异常: 线程“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;
}
}
非常感谢任何帮助!
答案 0 :(得分:3)
这不是多态的工作原理。您不能通过子类引用引用超类(或接口)“对象”。但是,您可以通过其实现接口的名称或任何超类来引用任何子类对象。
Comparable c = new Element();
或者一般来说,你可以记住,这总是合法的:
Object c = new String();
但这永远不行:
AnyClass m = new Object();
答案 1 :(得分:3)
这是因为Java Type Erasure。 要回答这个问题,我需要解释Unbounded Wildcards,Bounded Wildcards和Type Erasure。如果您熟悉它,请随意跳过任何部分。
这篇文章的内容是从java文档中汇总的。
使用通配符(
?
)指定无界通配符类型,例如List<?>
。这称为未知类型的列表。有两种情况,无界通配符是一种有用的方法:
如果您正在编写可以使用Object类中提供的功能实现的方法。
当代码使用泛型类中不依赖于类型参数的方法时。例如,
List.size
或List.clear
。事实上,Class<?>
经常被使用,因为Class<T>
中的大部分方法都不依赖于T
。
考虑一个简单的绘图应用程序,可以绘制矩形和圆形等形状。要在程序中表示这些形状,您可以定义类层次结构,例如:
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()
只能在精确形状的列表上调用:例如,它不能在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的子类。
类整数 * java.lang.Object * java.lang.Number * java.lang.Integer
ClassCastException
泛型被引入到Java语言中,以便在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java编译器将类型擦除应用于:
- 如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或对象。因此,生成的字节码只包含普通的类,接口和方法。
- 如有必要,插入类型转换以保护类型安全。
- 生成桥接方法以保留扩展泛型类型中的多态性。
在类型擦除过程中,Java编译器会擦除所有类型参数,如果type参数是有界的,则将每个参数替换为第一个绑定,如果type参数是无界的,则替换为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);
}
}
在类型擦除后,
Node
和MyNode
类成为:
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)
在类MyNode
的对象上执行。 (MyNode
类继承了setData(Object)
的{{1}}。)- 在
的正文中Node
,
setData(Object)
引用的对象的 data
字段已分配给n
。
- 可以访问通过
String
引用的同一对象的data
字段,并且该字段应为mn
(因为Integer
是mn
这是一个MyNode
。尝试将Node<Integer>
分配给String
会导致Java编译器在赋值时插入一个Integer
。
答案 2 :(得分: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
和super
的具体而神秘的规则;这是另一回事。)