Java generics class cast exception

好久不见. 提交于 2021-02-05 11:38:51

问题


I am trying to create a class that processes comparables. I boiled down to the simplest code that gives an error when I try to instantiate the class. I get a couple of compile warnings (unchecked cast) but when I run this program it throws a classcast exception. I did look at some of the other questions on this topic but didnt come across something useful.

public class GD<Item extends Comparable<Item>> {
   private Item[] data;
   private final int MAX_SIZE = 200;
   public GD() {
      data = (Item[]) new Object[MAX_SIZE];
   }

   public static void main(String[] args) {
     GD<String> g = new GD<String>();
   }
}

回答1:


The problem lies here:

data = (Item[]) new Object[MAX_SIZE];

You are instantiating an array of Object and then you try to cast it as an array of Item, which throws an exception because Object does not extend your Item class, because it does not implement Comparable. What you would like instead is:

data = new Item[MAX_SIZE];

But you can't do this because Item is a generic type. If you want to create objects (or arrays of objects) of this type dynamically, you need to pass the Class object to your GD class's constructor:

import java.lang.reflect.Array;

public class GD<Item extends Comparable<Item>> {
   private Item[] data;
   private final int MAX_SIZE = 200;
   public GD(Class<Item> clazz) {
      data = (Item[]) Array.newInstance(clazz, MAX_SIZE);
   }

   public static void main(String[] args) {
     GD<String> g = new GD<String>(String.class);
   }
}



回答2:


It is quite simple: Object does not implement Comparable<Object>, but you enforce, that Item extends Comparable<Item>. Therefore, you cannot cast Object[] into Item[].

To avoid the problem of creating an array of generic type Item, we can take a look at Java's ArrayList implementation. Here, the backing array is of type Object and only accessible via mutator methods:

private Object[] data; // Make the backing array private, access only via mutator methods

public void add(Item i)
{
    int idx = ... // calculate next free index somehow
    this.data[idx] = i;
}

public Index get(int idx) {
    return ((Item) (this.data[idx]));
}

Since you only allow Items to be inserted, you can only return Items and therefore you will never have a ClassCastException.

Some remark on your code: Typically, one uses T, U, S for type parameters. If you use things like Item, one may confuse the type parameter with an acutal class.




回答3:


The cast (Item[]) does not check the runtime type of the object against Item[], because what Item is is not known at runtime; instead it checks against the erasure of Item[], which is Comparable[] (because Comparable is the erasure of Item). An object whose actual runtime class is Object[] is not an instance of Comparable[], so it causes a ClassCastException. That's why simply doing

data = (Item[]) new Comparable[MAX_SIZE];

will make the exception go away.

So there are two ways to approach this:

  • The type-theoretically "safe" way, which is to use a fixed non-specific array type like Object[] or Comparable[] as the compile-time type of the data variable. That way you can create an array object of the same runtime type and safely assign it to the variable without unsafe casts. However, when you need to take something out of the array and return it as the generic type, you would have to do an explicit (unchecked) cast:

    public class GD<Item extends Comparable<Item>> {
       private Object[] data;
       private final int MAX_SIZE = 200;
       public GD() {
          data = new Object[MAX_SIZE];
       }
       public Item get(int index) {
          return (Item)data.get(index);
       }
    }
    
  • Alternately, you could keep data as Item[], by making an unsafe cast from new Comparable[] when you create it. Note that this is theoretically an invalid cast, as the array's actual runtime class is Comparable[], which is not an instance of Item[] in general. However, inside this class, Item is erased to Comparable, and this lie will not cause any problems, as long as the claim that data is Item[] is not exposed to the outside of the class (e.g. if data were public, or if there was a method like getArray() that returned data directly as Item[], then that would not be good). The advantage of this way is that you don't have to cast when you get something out of the array. The disadvantage is that it's up to the programmer to be careful to not expose this lie about the type to the outside.

    public class GD<Item extends Comparable<Item>> {
       private Item[] data;
       private final int MAX_SIZE = 200;
       public GD() {
          data = (Item[])new Comparable[MAX_SIZE];
       }
       public Item get(int index) {
          return data.get(index);
       }
    }
    


来源:https://stackoverflow.com/questions/31173283/java-generics-class-cast-exception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!