Java.lang.ClassCastException: [Ljava.lang.Comparable; can not be discarded

In my code, I got the following exception: An exception in the stream "main" java.lang.ClassCastException: [Ljava.lang.Comparable; cannot be used for [LElement; at the next 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!

+4
source share
3 answers

This is not how polymorphism works. You cannot reference the β€œobject” of a superclass (or interface) through a subclass reference. However, you can refer to any object of a subclass through the name of its implementation interface or any superclass.

 Comparable c = new Element(); 

Or, in general, you can remember that this is always legal:

 Object c = new String(); 

But this is never normal:

 AnyClass m = new Object(); 
+3
source

This is due to 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 was compiled from the java documentation.

1. Unlimited wildcards

An infinite type of substitution is specified using a wildcard character ( ? ), For example, List<?> . This is called an unknown type list. There are two scenarios in which an unlimited wildcard is a useful approach:

  • If you are writing a method that can be implemented using the functions provided in the Object class.

  • When the code uses methods in a general class that are independent of the type parameter. For example, List.size or List.clear . In fact, Class<?> used so often because most methods from Class<T> are independent of T

2. Limited wildcards

Consider a simple drawing application that can draw shapes such as rectangles and circles. To represent these forms within a program, you can define a class hierarchy, such as:

 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 canvas:

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

Any drawing usually contains several figures. Assuming they are presented 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 type rules say that drawAll() can only be called in lists exactly Shape: it cannot, for example, be called on List<Circle> . This is unfortunate, since the whole method does, reads the figures from the list, so it can just as easily be called on the List<Circle> . We really want the method to take a list of any form:

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

Here is a small but very important difference: we have replaced the type of List<Shape> with List<? extends Shape> List<? extends Shape> . Now drawAll() will accept lists of any subclass of Shape , so we can now call it if we want.

List<? extends Shape> List<? extends Shape> is an example of a limited template. ? denotes an unknown type, but in this case we know that this unknown type is actually a subtype of Shape. (Note: this may be the form itself or some subclass, it should not literally expand the form.) We say that Shape is the upper boundary of the wildcard.

Similarly, the syntax ? super T ? super T , which is a limited wildcard, indicates an unknown type, which is a supertype of T. For example, ArrayedHeap280 includes ArrayedHeap280<Integer> , ArrayedHeap280<Number> and ArrayedHeap280<Object> . As you can see in the java documentation for the Integer class , Integer is a subclass of Number, which, in turn, is a subclass of Object.

Class Integer * java.lang.Object * java.lang.Number * java.lang.Integer

3. Type Erasure and ClassCastException

Generalizations have been introduced into the Java language to provide more stringent type checks at compile time and to support general programming. To implement generics, the Java compiler applies style erasure to:

  • Replace all type parameters in generic types with your borders or object if the type parameters are not limited. Thus, the obtained bytecode contains only ordinary classes, interfaces, and methods.
  • Insert the type if necessary to maintain type safety.
  • Creating bridge methods for preserving polymorphism in extended generic types.

In the process of erasing a type, the Java compiler erases all the type parameters and replaces each of its first bindings if the type parameter is limited, or Object if the type parameter is unlimited.

Consider the following general class that represents a node in a separately 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, a generic node class uses a parameter of a restricted type:

 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 parameter of a restricted type T with the first associated 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 erasing the styles, the Node and MyNode 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 erasing the type, this code will look like this:

 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's what happens when the code runs:

  • n.setData("Hello"); calls the setData(Object) method on an object of class MyNode . (The MyNode class inherited setData(Object) from Node .)
  • In the body of setData(Object) ,

Field

data object referenced by n is assigned a String .

  • The data field of the same object referenced via mn may be accessible and expected to be an Integer (since mn is MyNode , which is a Node<Integer> . ClassCastException String to Integer raises a ClassCastException from the cast inserted into the assignment by the compiler Java
+3
source

Arrays cannot be subjected to the same polymorphic method that classes can execute. Consider this code:

 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! 

Naturally, this code does not make sense; you would put Down in your bunch of Elements, and that's just not true. The inconsistency is that the opposite does not work either:

 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! 

The consequence of this is that arrays cannot be dropped in any direction. (Generics can, but with specific and secret rules about extends and super ; that's another matter.)

+2
source

Source: https://habr.com/ru/post/1497610/


All Articles