Weird runtime error (ClassCastException while declaring)
Asked Answered
U

1

1

I have the following code within a program I'm making:

01  public class Clazz<T>
02  {
03    T[] t;
04    
05    public Clazz<T> methodA(int... ints)
06    {
07      Clazz<Integer> ints2 = new Clazz<>();
08      int remInd[] = new int[t.length - ints2.t.length];
09      return this;
10    }
11  }

but when I run method methodA, I get this error:

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
    at Clazz.methodA(Clazz.java:8)

Why would I get this error? Of course, the code I've shown is incomplete compared to the huge class in question (for instance, the array t won't be empty when checking its length), but I believe I've shown everything that matters. Why won't this run?

A note: I am making this with JDK 1.7, so that's why line 7 compiles and works

Working Solution


I decided, for whatever reason, to implement the following solution, and it worked:

01  public class Clazz<T>
02  {
03    T[] t;
04    
05    public Clazz<T> methodA(int... ints)
06    {
07      Clazz<Integer> ints2 = new Clazz<>();
08      int remInd[] = new int[t.length - ints2.length()];
09      return this;
10    }
11    
12    public int length()
13    {
14      return t.length;
15    }
16  }

Though this is a solution, I would still like to know why it works.

Unseam answered 30/6, 2011 at 16:32 Comment(8)
You are missing part of the code, namely where t is initialized. This matters.Dyspeptic
Does line 7 really compile for you?Busk
like I said, this is an 11-line version of an 841-line class. If I showed you everything that went on, this example would be bigger than needed. Just be sure that everything compiles, and t is always initialized (I have several constructors, all of which ensure this). Also, see the latest editUnseam
@Supuhstar: I said that specific thing matters because that specific thing does matter. I didn't ask for the other 829 lines, just that one specific one. How you initialize t determines whether or not you'll get that error. In this case, I had to assume you were initializing it with an Object[].Dyspeptic
I pass it to a constructor that looks like this: public Clazz(javax.swing.JList/*<T>*/ jList) { this(jList.getModel().getSize()); for (int i=0; i < t.length; i++) t[i] = (T)jList.getModel().getElementAt(i); } which in turn uses the constructor that looks like this: public Clazz(int init) throws NegativeArraySizeException { if (init < 0) throw new NegativeArraySizeException(init + " is less than 0 (the minimum array size)"); t = new Clazz<T>(null, null).clear().t; for (int i=0; i < init; i++) add(null); }Unseam
which in turn uses a constructor that looks like this: public Clazz(T... array) { t = java.util.Arrays.copyOf(array, array.length); } and a method that looks like this: public Clazz<T> add(T val) { t = java.util.Arrays.copyOf(t, t.length + 1); t[t.length - 1] = val; return this; }Unseam
@Supuhstar: you still might run into trouble at other points when trying to access the array. You might consider taking the advice in my answer and not declaring a generic array at all. They are never a good idea. If you have a generic class backed by an array, then that array should be 100% encapsulated in which case there is no reason not to use an Object[]. Regarding why your "solution" works, I explain that in my response to @DHall's comment on my answer.Dyspeptic
I've found some clever work-arounds for everything after being inspired by your solution, Mark! Thank you!Unseam
D
7

You are missing the code that initializes T, but I'm going to assume it looks something like this. I've added a few lines that don't change any functionality but which will help demonstrate the error:

public class Clazz<T> {
    T[] t = (T[]) new Object[5];

    public Clazz<T> methodA(int... ints) {
        Clazz<Integer> ints2 = new Clazz<Integer>();
        int l1 = t.length;
        int l2 = ints2.t.length;
        int remInd[] = new int[l1 - l2];
        return this;
    }

    public static void main(String...args) {
        Clazz<String> clazz = new Clazz<String>();
        clazz.methodA(54, 7);
    }
}

With this code I could reproduce the error. The problem here is in this code:

int l2 = ints2.t.length

Since the compiler knows the type parameter for ints2 and thus ints2.t, this can be thought of as the rough equivalent of this:

Integer[] temp = ints2.t;
int l2 = temp.length;

It is in the implicit cast to Integer[] (whose class simple name is [Ljava.lang.Integer) that this fails, since t is an Object[] and not an Integer[], and one cannot be cast to the other.

Working with generic arrays

There are many complications from working with arrays declared over a generic type that are documented elsewhere. In short, I'll say that if you need to have a "generic array" instead consider declaring and using it as an Object[] in every way, except that when you interact with a client of the class, you either accept or return only a T instead of an Object (for returning, via an unchecked cast). For example,

Object[] t = new Object[5];

public T getSomethingFromArray() {
    return (T)t[2];
}

public void setSomethingInArray(T something) {
    t[2] = something;
}

This is how ArrayList works, by the way. Have a look at its code on DocJar.

Edit

Generic arrays aside, I don't think you understand the idea of the implicit cast. Here's much shorter code that fails with essentially the same error:

public class Clazz<T> {
    T t = (T) new Object();

    public static void main(String...args) {
        Clazz<String> clazz = new Clazz<String>();
        clazz.t.toString();
    }
}

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Clazz.main(Clazz.java:6)
    ...

Even though there is no need to cast clazz.t to a String, it does so implicitly simply by referencing clazz.t. Here is the javap -c output for that compiled class:

Compiled from "Clazz.java"
public class Clazz extends java.lang.Object{
java.lang.Object t;

public Clazz();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   new #2; //class java/lang/Object
   8:   dup
   9:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   12:  putfield    #3; //Field t:Ljava/lang/Object;
   15:  return

public static void main(java.lang.String[]);
  Code:
   0:   new #4; //class Clazz
   3:   dup
   4:   invokespecial   #5; //Method "<init>":()V
   7:   astore_1
   8:   aload_1
   9:   getfield    #3; //Field t:Ljava/lang/Object;
//BELOW is the line that will fail
   12:  checkcast   #6; //class java/lang/String
   15:  invokevirtual   #7; //Method java/lang/String.toString:()Ljava/lang/String;
   18:  pop
   19:  return

}

In the case of your original code, here is the javap -c output of methodA():

public Clazz methodA(int[]);
  Code:
   0:   new #5; //class Clazz
   3:   dup
   4:   invokespecial   #6; //Method "<init>":()V
   7:   astore_2
   8:   aload_0
   9:   getfield    #4; //Field t:[Ljava/lang/Object;
   12:  arraylength
   13:  aload_2
   14:  getfield    #4; //Field t:[Ljava/lang/Object;
//BELOW is the line that will fail
   17:  checkcast   #7; //class "[Ljava/lang/Integer;"
   20:  arraylength
   21:  isub
   22:  newarray int
   24:  astore_3
   25:  aload_0
   26:  areturn
Dyspeptic answered 30/6, 2011 at 16:51 Comment(6)
how does this affect the .length object of every array? Isn't that always an int? What's wrong with ints2.t.length?Unseam
@Supuhstar: The result is an int, but in between the compiler must use the reference ints2.t which includes an implicit cast. I believe it would fail no matter what you did after getting the reference. You could do ints2.t.toString() and it would fail just the same.Dyspeptic
@DHall: The implicit cast doesn't exist in that case because you are within the generic class instance where all type information is erased (T is erased to Object). Therefore it would only be trying to implicitly cast to Object[].Dyspeptic
As for your suggestion for t to be an Object[], I'm afraid that won't work, as I do quite a bit of clazzObj.t instanceof Clazz2Unseam
@Supuhstar: I'm not sure what you think the problem is there...t instanceof Clazz2 will of course always fail since t is an array, but regardless what you declare a variable (in this case t) as will never affect its runtime type which is what will be checked with an instanceof.Dyspeptic
sorry, I meant to say clazzObj.t instanceof Clazz2[]Unseam

© 2022 - 2024 — McMap. All rights reserved.