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

ArrayList использует массив объектов внутри:

private transient Object[] elementData;

и в E get(int) метод, который он использует для типа E.

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

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

Если это используется E [], этот код ниже не нужен

return (E) elementData[index];

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

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

EDITED

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

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

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

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

Ответ 1

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


Генераторы Java предназначены для того, чтобы выглядеть и чувствовать себя как настоящие, reified, мульти-инстанцированные, С++ или С# -типы. Это означает, что для такого типа, как 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 в трудно предсказанных местах. Что делать, если Java 9 представила обновленные дженерики? Первая реализация продолжила бы работать, но это сломалось бы.

Вот почему большинство разумных соглашений о кодировании Java требуют, чтобы неконтролируемые отбрасывания были правильными по типу. (E) elementData[i] является корректным, потому что ArrayList гарантирует, что в elementData можно сохранить только E. (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