Почему Java ArrayList использует кастинг для каждого элемента вместо кастинга для каждого массива?
Что происходит внутри Java ArrayList<T>
(и, вероятно, многих других классов), заключается в том, что существует внутренний Object[] array = new Object[n];
, к которому записываются T
Objects. Всякий раз, когда элемент считывается из него, выполняется листинг return (T) array[i];
. Итак, бросок на каждом прочтении.
Интересно, почему это делается. Мне кажется, что они просто делают ненужные броски. Не было бы более логичным, а также немного быстрее просто создать T[] array = (T[]) new Object[n];
, а затем просто return array[i];
без трансляции? Это всего лишь одно действие при создании массива, которое, как правило, намного меньше количества прочитанных.
Почему их метод предпочтительнее? Я не понимаю, почему моя идея не совсем лучше?
Ответы
Ответ 1
Это сложнее: generics стираются в байтовом коде, а стирание T[]
- Object[]
. Аналогично, возвращаемое значение get()
становится Object
. Чтобы сохранить целостность системы типов, проверенный листинг вставляется, когда класс фактически используется, то есть
Integer i = list.get(0);
будет стерто до
Integer i = (Integer) list.get(0);
Таким образом, проверка любого типа внутри ArrayList является избыточной. Но это действительно так, потому что как (T)
, так и (T[])
- неконтролируемые отбрасывания и не накладывают на них лишних затрат времени исполнения.
Можно написать проверенный ArrayList, который делает:
T[] array = Array.newInstance(tClass, n);
Это предотвратит загрязнение кучи, но по цене проверки избыточного типа (вы не можете подавить синтетический бросок в вызывающем коде). Это также потребовало бы, чтобы вызывающий пользователь предоставил ArrayList объекту класса типа элемента, который загромождает его api и затрудняет его использование в общем коде.
Изменить: почему запрещено создание общего массива?
Одна проблема заключается в том, что массивы проверены, а дженерики не отмечены. То есть:
Object[] array = new String[1];
array[0] = 1; // throws ArrayStoreException
ArrayList list = new ArrayList<String>();
list.add(1); // causes heap pollution
Следовательно, имеет значение тип компонента массива. Я предполагаю, что поэтому разработчики языка Java требуют от нас явного указания того, какой тип компонента использовать.
Ответ 2
Всякий раз, когда элемент считывается из него, выполняется листинг return (T) array[i];
. Итак, бросок на каждом прочтении.
Общий - это проверка времени компиляции. Во время выполнения вместо этого используется тип T extends. В этом случае T
неявно extends Object
, так что у вас есть во время выполнения.
return (Object) array[i];
или
return array[i];
Не было бы более логичным, а также немного быстрее просто создать
T[] array = (T[]) new Object[n]
Не совсем. Снова во время выполнения это становится
Object[] array = (Object[]) new Object[n];
или
Object[] array = new Object[n];
Что вы на самом деле ловите рыбу?
T[] array = new T[n];
за исключением того, что это не скомпилируется, главным образом потому, что T не известно во время выполнения.
Что вы можете сделать, это
private final Class<T> tClass; // must be passed in the constructor
T[] array = (T[]) Array.newInstance(tClass, n);
только тогда массив действительно будет ожидаемым типом. Это может сделать чтение быстрее, но ценой записи. Основным преимуществом будет проверка с ошибкой, т.е. Вы остановите поврежденную коллекцию, а не подождите, пока не обнаружите, что она была повреждена, чтобы выбросить исключение.
Ответ 3
Я думаю, что это скорее вопрос стиля кода, а не производительности или безопасности типа (поскольку массив поддержки является закрытым)
java 5 ArrayList
был реализован так, как вы предложили с массивом E[]
. Если вы посмотрите на исходный код, вы увидите, что он содержит 7 (E []). Из java 6 ArrayList
изменилось использование массива Object[]
, в результате чего было выполнено только 3 (E).
Ответ 4
Массив тоже объект.
Здесь T[] array = (T[]) new Object[n]
вы бросаете только (T []) тип объекта, а не элементы в массиве.