Причина, по которой существует ClassCastException
- не может быть отлита от Object[]
до String[]
- это из-за того, что делает компилятор при использовании дженериков. При вызове returnItems()
компилятор вставляет листинг в String[]
, потому что returnItems
возвращает T[]
. Тип стирания при компиляции означает, что он возвращает Object[]
, но так как T
здесь String
, компилятор вставляет литье в String[]
. Но исходный объект arrayOfItems
не является String[]
, это Object[]
, поэтому литье не получается.
Это должно было привести к предупреждению «непроверенного броска» во время компиляции, от Object[]
до T[]
.
Вместо этого вам следует следовать рекомендациям в How to create a generic array in Java? в создании общего массива.
Примите Class<T>
в вашем конструкторе, чтобы вы могли позвонить Array.newInstance
и получить T[]
с самого начала.
@SuppressWarnings("unchecked") // This suppression is safe.
public Playground(int size, Class<T> clazz){
this.size = size;
pos = 0;
arrayOfItems = (T[]) Array.newInstance(clazz, size);
}
Тогда вы можете создать animals
пропусканием String.class
:
Playground<String> animals = new Playground<String>(5, String.class);
Update
Ниже приводится разумное объяснение, почему первый пример работы (относящий Object[]
), когда второй пример не работает (доступ к полю length
непосредственно по типу возврата returnItems()
metho д.
Первый пример
Object[] s = animals.returnItems();
for(int i=0; i < s.length;i++) {
System.out.println(s[i]);
}
JLS, Section 5.2, описывает «Присваивающие контексты», которые регулируют то, что происходит при присвоении значения из выражения переменной.
Единственное исключение, которое может возникнуть в результате преобразования в контексте присваивания являются:
- ClassCastException, если, после того, как конверсии выше, были применены, полученное значение представляет собой объект, который не является экземпляром подкласс или субинтерфейс стирания (§4.6) типа переменной.
Это обстоятельство может возникнуть только в результате загрязнения кучи (§4.12.2). На практике реализации должны выполнять только приведения в действие при доступе к полю или методу объекта параметризованного типа, когда стираемый тип поля или стираемый тип возвращаемого метода отличаются от его ненарушенного типа.
...
Компилятор не нужно вставить слепок в String[]
здесь. Когда поле length
доступно для доступа позже, переменная уже имеет тип Object[]
, поэтому здесь нет никаких проблем.
Второй пример
for(int i=0; i < animals.returnItems().length;i++) {
System.out.println(animals.returnItems()[i]);
}
ClassCastException
здесь, кажется, не зависит от цикла for
; эта ошибка будет происходить с простой печатью длиной:
System.out.println(animals.returnItems().length);
Это выражение а поля доступа, покрывается JLS, Section 15.11.1.
Идентификатор [T] he называет одно доступное поле элемента в типе T, а тип выражения доступа к полю - это тип поля члена после преобразования захвата (§5.1.10).
Преобразование захвата фиксирует тип как String[]
. Компилятор должен вставить приведение в String[]
по той же причине, что и вставки роли для вызова метода. Поле или метод могут существовать только на захваченном типе.
Потому что тип arrayOfItems
действительно Object[]
, литье не работает.
Как описано выше, создание общего массива с Array.newInstance
решает эту проблему, поскольку создается фактическое String[]
. С этими изменениями вставленный листинг все еще присутствует, но на этот раз он преуспевает.
Можете ли вы показать стек? – jervine10
@ jervine10 Я признаюсь, что я действительно не знаю, как это сделать (или получить от него что-то значимое). У меня еще есть чему поучиться :) –
Весь кастинг массивов кажется странным. 'Object []' не является экземпляром 'String []', поэтому на самом деле он должен бросать 'ClassCastException' прямо в конструкторе. Чтобы узнать, что происходит, вам, вероятно, потребуется проверить байт-код. –