2015-01-16 2 views
6

Я отправил ответ на вопрос different question, когда я натолкнулся на маленькую тайну. Определение класса (слегка модифицирована от оригинального спрашивающего) здесь:Возврат массива может использоваться в задании, но не в цикле

public class Playground<T>{ 
    private int pos; 
    private final int size; 
    private T[] arrayOfItems; 
    public Playground(int size){ 
     this.size = size; 
     pos = 0; 
     arrayOfItems = (T[]) new Object[size]; 
    } 

    public void addItem(T item) { 
     arrayOfItems[pos] = item; 
     pos++; 
    } 

    public void displayItems() { 
     for(int i = 0;i<pos;i++){ 
      System.out.println(arrayOfItems[i]); 
     } 
    } 

    public T[] returnItems() { 
     return (T[]) arrayOfItems; 
    } 
} 

В основном, мы затем создать новую спортивную площадку, Playground<String> animals = new Playground<String>(5); и поставить некоторые струны животных в нем. (Собака, кошка и т. Д.).

Тайна в том, что это работает:

Object[] s = animals.returnItems(); 
for(int i=0; i < s.length; i++) { 
     System.out.println(s[i]); 
} 

Но это создает ClassCastExceptionв декларации для цикла.

for(int i=0; i < animals.returnItems().length; i++) { 
     System.out.println(animals.returnItems()[i]); 
} 

Оба Object[] с и String[] с иметь переменные длины. Почему использование метода accessor в объявлении цикла вызывает исключение?

+2

Можете ли вы показать стек? – jervine10

+0

@ jervine10 Я признаюсь, что я действительно не знаю, как это сделать (или получить от него что-то значимое). У меня еще есть чему поучиться :) –

+1

Весь кастинг массивов кажется странным. 'Object []' не является экземпляром 'String []', поэтому на самом деле он должен бросать 'ClassCastException' прямо в конструкторе. Чтобы узнать, что происходит, вам, вероятно, потребуется проверить байт-код. –

ответ

7

Причина, по которой существует 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[]. С этими изменениями вставленный листинг все еще присутствует, но на этот раз он преуспевает.

+0

Но почему это работает в первом случае? –

+0

Похоже, что компилятору не нужно вставлять приведение, когда целевым типом присваивания является 'Object []'. – rgettman

+0

Я добавил более подробное объяснение, почему компилятору не нужно вставлять актерский состав в первый пример и почему ему нужно вставить актерский состав во второй пример. – rgettman

Смежные вопросы