Странная ошибка времени выполнения (ClassCastException при объявлении)

У меня есть следующий код в программе, которую я делаю:

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  }

но когда я запускаю метод methodA, я получаю эту ошибку:

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)

Почему я получаю эту ошибку? Конечно, код, который я показал, неполный по сравнению с рассматриваемым огромным классом (например, массив t не будет пустым при проверке его длины), но я считаю, что показал все, что имеет значение. Почему это не запускается?

Примечание: я делаю это с помощью JDK 1.7, поэтому строка 7 компилируется и работает.

Рабочее решение


По какой-то причине я решил реализовать следующее решение, и оно сработало:

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  }

Хотя это решение, я все же хотел бы знать, почему оно работает.


person Ky Leggiero    schedule 30.06.2011    source источник
comment
Вам не хватает части кода, а именно где инициализируется t. Это важно.   -  person Mark Peters    schedule 30.06.2011
comment
Действительно ли строка 7 компилируется для вас?   -  person Perception    schedule 30.06.2011
comment
как я уже сказал, это 11-строчная версия 841-строчного класса. Если бы я показал вам все, что происходило, этот пример был бы больше, чем нужно. Просто убедитесь, что все компилируется, а t всегда инициализируется (у меня есть несколько конструкторов, каждый из которых обеспечивает это). Также см. последнее редактирование   -  person Ky Leggiero    schedule 30.06.2011
comment
@Supuhstar: Я сказал, что конкретная вещь имеет значение, потому что эта конкретная вещь действительно имеет значение. Я не просил другие 829 строк, только одну конкретную. То, как вы инициализируете t, определяет, получите ли вы эту ошибку. В этом случае я должен был предположить, что вы инициализируете его с помощью Object[].   -  person Mark Peters    schedule 30.06.2011
comment
Я передаю его конструктору, который выглядит так: 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); }, который, в свою очередь, использует конструктор, который выглядит так: 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); }   -  person Ky Leggiero    schedule 30.06.2011
comment
который, в свою очередь, использует конструктор, который выглядит так: public Clazz(T... array) { t = java.util.Arrays.copyOf(array, array.length); } и метод, который выглядит так: public Clazz<T> add(T val) { t = java.util.Arrays.copyOf(t, t.length + 1); t[t.length - 1] = val; return this; }   -  person Ky Leggiero    schedule 30.06.2011
comment
@Supuhstar: у вас все равно могут возникнуть проблемы в других точках при попытке доступа к массиву. Вы можете подумать о том, чтобы воспользоваться советом в моем ответе и вообще не объявлять общий массив. Они никогда не бывают хорошей идеей. Если у вас есть универсальный класс, поддерживаемый массивом, то этот массив должен быть инкапсулирован на 100%, и в этом случае нет причин не использовать Object[]. Что касается того, почему ваше решение работает, я объясняю это в своем ответе на комментарий @DHall к моему ответу.   -  person Mark Peters    schedule 30.06.2011
comment
Я нашел несколько умных обходных путей для всего, вдохновленный вашим решением, Марк! Благодарю вас!   -  person Ky Leggiero    schedule 02.07.2011


Ответы (1)


Вам не хватает кода, который инициализирует T, но я предполагаю, что он выглядит примерно так. Я добавил несколько строк, которые не меняют никакой функциональности, но помогут продемонстрировать ошибку:

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);
    }
}

С помощью этого кода я смог воспроизвести ошибку. Проблема здесь в этом коде:

int l2 = ints2.t.length

Поскольку компилятор знает параметр типа для ints2 и, следовательно, ints2.t, это можно рассматривать как грубый эквивалент этого:

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

Именно в неявном приведении к Integer[] (простое имя которого [Ljava.lang.Integer) это терпит неудачу, поскольку t является Object[], а не Integer[], и одно нельзя привести к другому.

Работа с универсальными массивами

Есть много сложностей при работе с массивами, объявленными поверх универсального типа, которые описаны в других источниках. Вкратце, я скажу, что если вам нужен «общий массив», вместо этого рассмотрите возможность объявления и использования его как Object[] любым способом, кроме того, что когда вы взаимодействуете с клиентом класса, вы либо принимаете, либо возвращаете только T вместо Object (для возврата через непроверенное приведение). Например,

Object[] t = new Object[5];

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

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

Кстати, так работает ArrayList. Взгляните на его код в DocJar.

Редактировать

Помимо общих массивов, я не думаю, что вы понимаете идею неявного приведения типов. Вот гораздо более короткий код, который завершается практически с той же ошибкой:

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)
    ...

Несмотря на то, что нет необходимости приводить clazz.t к строке, это делается неявно, просто путем ссылки на clazz.t. Вот вывод javap -c для этого скомпилированного класса:

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

}

В случае вашего исходного кода, вот вывод javap -c 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
person Mark Peters    schedule 30.06.2011
comment
как это влияет на объект .length каждого массива? Разве это не всегда int? Что не так с ints2.t.length? - person Ky Leggiero; 30.06.2011
comment
@Supuhstar: результатом является int, но между ними компилятор должен использовать ссылку ints2.t, которая включает неявное приведение. Я считаю, что это потерпит неудачу, независимо от того, что вы сделали после получения ссылки. Вы можете сделать ints2.t.toString(), и это все равно потерпит неудачу. - person Mark Peters; 30.06.2011
comment
@DHall: в этом случае неявного приведения не существует, потому что вы находитесь внутри экземпляра универсального класса, где вся информация о типе стирается (T стирается до Object). Следовательно, это будет попытка неявного приведения к Object[]. - person Mark Peters; 30.06.2011
comment
Что касается вашего предложения, чтобы t стал Object[], боюсь, это не сработает, так как я немного занимаюсь clazzObj.t instanceof Clazz2 - person Ky Leggiero; 30.06.2011
comment
@Supuhstar: я не уверен, в чем, по вашему мнению, проблема... t instanceof Clazz2, конечно, всегда будет давать сбой, поскольку t - это массив, но независимо от того, что вы объявляете переменную (в данном случае t) as никогда не повлияет на его тип времени выполнения, который будет проверен с помощью instanceof. - person Mark Peters; 30.06.2011
comment
извините, я хотел сказать clazzObj.t instanceof Clazz2[] - person Ky Leggiero; 02.07.2011