2014-09-05 4 views
10

ArrayList использует массив объектов внутри:Почему ArrayList использует Object [] (вместо E []) внутри?

private transient Object[] elementData; 

и в E get(int) методе это приведение к типу Е.

Мой вопрос: почему ArrayList не использует E [] для хранения объектов?

Я понимаю, что после запуска компилятора стирание типа преобразует E [] в Object [], но все равно нужно отбрасывать E в каждом вызове get()?

Если использовать его E [] это ниже код не является необходимым

return (E) elementData[index]; 

Выбор использования объекта [] для работы?

Как преобразование стирания E [] в Object [], java делает литье внутри, чтобы возвращать правильный тип в общих методах?

EDITED

Позвольте мне объяснить лучше, чем смысл моего сомнения:

Если ArrayList использование E [] вместо Object [], в методе получения (интермедиат) бросок не необходимо. Это увеличит производительность (по-видимому).

Но нет никакой магии, я думаю, что использование E [] JVM будет лить объект в любом случае, потому что стирание типа будет преобразовано в Object. Верный?

ps: извините за мой плохой английский.

+0

Вы, кажется, думают, что «E» является реальным. Это Финг Ньютон вашего воображения. Класс массива фиксирован в то время, когда класс ArrayList компилируется, обратно в литейный цех Java. –

+0

@HotLicks «Я понимаю, что после запуска компилятора тип-стирание преобразует E [] в Object []« .. »Если использовать E [] этот код ниже [это не обязательно] [использовать приведения] – user2864740

+0

Возможно, исходный код был улучшен, объявив E [], но окончательный результат компиляции был бы таким же. –

ответ

8

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


Java дженериков разработаны, чтобы выглядеть и чувствовать себя как настоящие, овеществленная, мульти-экземпляр, C++ - или C# -стиль дженериков. Это означает, что для такого типа, как ArrayList<E>, мы ожидаем, что ArrayList<String> будет вести себя так, как будто в каждом случае E был заменен на String. Другими словами, это:

private Object[] elementData = new Object[size]; 

public E get(int i) { 
    return (E) elementData[i]; 
} 

String str = list.get(0); 

должно стать следующее:

private Object[] elementData = new Object[size]; 

public String get(int i) { 
    return (String) elementData[i]; 
} 

String str = list.get(0); 

Теперь, как вы, наверное, знаете, что это на самом деле не как они работают. По соображениям обратной совместимости, которые сейчас (в основном) давно позади нас, Java-дженерики реализуются с помощью стирания типа, где E фактически заменяется на Object всюду, а листы до String вставляются в , вызывая код, где это необходимо. Это означает, что код на самом деле становится чем-то вроде этого:

private Object[] elementData = new Object[size]; 

public Object get(int i) { 
    return elementData[i]; 
} 

String str = (String) list.get(0); 

Актерский к (E) исчез, и снова появился на месте вызова. Если сайт вызова проигнорировал результат, бросок исчез бы полностью! Вот почему он дал «непроверенное» предупреждение.


Теперь представьте себе, если elementData имел тип E[] вместо этого, как вы предлагаете. То есть, код выглядит следующим образом:

private E[] elementData = (E[]) new Object[size]; 

public E get(int i) { 
    return elementData[i]; 
} 

String str = list.get(0); 

Мы знаем, что трансформируется в одно и то же, как и выше, из-за стирания. Но если бы мы овеществлённая дженерики, как мы хотим, мы сделали, это будет выглядеть следующим образом:

private String[] elementData = (String[]) new Object[size]; 
// ClassCastException: Object[] is not a String[] 

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

И это хрупкое! Мы избегаем сбоев во время выполнения, потому что массив никогда не ускользает от класса. Но если бы это было так, это вызвало бы ClassCastException s в труднодоступных местах. Что делать, если Java 9 представила обновленные дженерики? Первая реализация продолжила бы работать, но это сломалось бы.

Вот почему наиболее разумные соглашения о кодировании Java требуют, чтобы неконтролируемые отбрасывания были правильными по типу. (E) elementData[i] является правильным, потому что ArrayList гарантирует, что только E s может быть сохранен в elementData. (E[]) new Object[size] никогда не соответствует правилу, если E не является Object.


Есть и другие преимущества. В Java 8, elementData поле может принимать специальные значения дозорных:

/** 
* Shared empty array instance used for empty instances. 
*/ 
private static final Object[] EMPTY_ELEMENTDATA = {}; 

/** 
* Shared empty array instance used for default sized empty instances. We 
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when 
* first element is added. 
*/ 
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 

/** 
* The array buffer into which the elements of the ArrayList are stored. 
* The capacity of the ArrayList is the length of this array buffer. Any 
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
* will be expanded to DEFAULT_CAPACITY when the first element is added. 
*/ 
transient Object[] elementData; // non-private to simplify nested class access 
+0

Я не уверен, что существование этих специальных значений дозорного является * причиной для * или * результатом * объявления 'elementData;' как 'Object []'. – Pshemo

+2

@Pshemo 'static E []' является незаконным –

+2

Они все равно могут оставить поля 'static' как' Object [] 'и иметь поле экземпляра типа' E [] ', кастинг при назначении. –