I got the following exception in my code: Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Comparable; cannot be cast to [LElement; at the following call:
Element<K,V>[] heap = (Element<K,V>[]) new Comparable[size];
where Element is defined as follows:
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;
}
}
any help is greatly appreciated!
This happens because of Java Type Erasure. To answer this question I need to explain Unbounded Wildcards, Bounded Wildcards, and Type Erasure. Feel free to skip any part if you are familiar with it.
The content of this post has been assembled from java documentation.
The unbounded wildcard type is specified using the wildcard character (
?
), for example,List<?>
. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach:
If you are writing a method that can be implemented using functionality provided in the Object class.
When the code is using methods in the generic class that don't depend on the type parameter. For example,
List.size
orList.clear
. In fact,Class<?>
is so often used because most of the methods inClass<T>
do not depend onT
.
Consider a simple drawing application that can draw shapes such as rectangles and circles. To represent these shapes within the program, you could define a class hierarchy such as this:
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) {
...
}
}
These classes can be drawn on a canvas:
public class Canvas {
public void draw(Shape s) {
s.draw(this);
}
}
Any drawing will typically contain a number of shapes. Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:
public void drawAll(List<Shape> shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
Now, the type rules say that
drawAll()
can only be called on lists of exactly Shape: it cannot, for instance, be called on aList<Circle>
. That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on aList<Circle>
. What we really want is for the method to accept a list of any kind of shape:
public void drawAll(List<? extends Shape> shapes) {
...
}
There is a small but very important difference here: we have replaced the type
List<Shape>
withList<? extends Shape>
. NowdrawAll()
will accept lists of any subclass ofShape
, so we can now call it on aList<Circle>
if we want.
List<? extends Shape>
is an example of a bounded wildcard. The?
stands for an unknown type, however, in this case, we know that this unknown type is in fact a subtype of Shape. (Note: It could be Shape itself, or some subclass; it need not literally extend Shape.) We say that Shape is the upper bound of the wildcard.
Similarly, the syntax ? super T
, which is a bounded wildcard, denotes an unknown type that is a supertype of T.
A ArrayedHeap280, for example, includes ArrayedHeap280<Integer>
, ArrayedHeap280<Number>
, and ArrayedHeap280<Object>
.
As you can see in the java documentation for Integer class, Integer is a subclass of Number that in turn is a subclass of Object.
Class Integer * java.lang.Object * java.lang.Number * java.lang.Integer
ClassCastException
Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:
- Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
- Insert type casts if necessary to preserve type safety.
- Generate bridge methods to preserve polymorphism in extended generic types.
During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.
Consider the following generic class that represents a node in a singly linked list:
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; }
// ...
}
In the following example, the generic Node class uses a bounded type parameter:
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; }
// ...
}
The Java compiler replaces the bounded type parameter T with the first bound class, 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);
}
}
After type erasure, the
Node
andMyNode
classes become:
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);
}
}
Consider the following code:
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.
After type erasure, this code becomes:
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.
Here is what happens as the code is executed:
n.setData("Hello");
causes the methodsetData(Object)
to be executed on the object of classMyNode
. (TheMyNode
class inheritedsetData(Object)
fromNode
.)- In the body of
setData(Object)
,the
data
field of the object referenced by n
is assigned to a String
.
- The
data
field of that same object, referenced viamn
, can be accessed and is expected to be anInteger
(sincemn
is aMyNode
which is aNode<Integer>
.Trying to assign aString
to anInteger
causes aClassCastException
from a cast inserted at the assignment by a Java compiler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With