Создание массива общих коллекций

Собственно, вопрос должен быть

Creating an array of generic anything.

Почему компилятор не может позаботиться об этом?

Следующее будет помечено как ошибка - не может создать общий массив.

List<MyDTO>[] dtoLists = {new ArrayList<MyDTO>(), anExistingDtoList};

Чтобы преодолеть это, мне нужно

List<MyDTO>[] dtoLists = (List<MyDTO>[])Array.newInstance(ArrayList.class, 2);
dtoLists[0] = new ArrayList<MyDTO>();
dtoLists[1] = anExistingDtoList;

Итак, почему компилятор не может преобразовать первый случай во второй случай?

Я понимаю, что generics являются определяющими, а не определяемыми временем выполнения, а массивы задаются временем выполнения и поэтому нуждаются в определенном типе для создания массива.

Каковы будут конструкторы компиляторов технологического/логического барьера, которые помешали бы им реализовать это?

Является ли проблема чисто философской, касающейся ортогональности языка? Если да, то как такое поведение нарушает ортогональность языка?

Это вопрос сложности? Объясните сложность.

Я надеюсь, что ответы на мой вопрос помогут мне лучше понять поведение java-компилятора, когда речь идет о дженериках.

Боковое примечание: c'mon перестанет быть счастливым. Ответы Массив общего списка не отвечайте на мой вопрос. Почему компиляторы не могут самопроизвольно выполнять преобразование?

Ответы

Ответ 1

На самом деле Java создает общий массив для varargs, поэтому вы можете сделать

List<MyDTO>[] dtoLists = array(new ArrayList<MyDTO>(), anExistingDtoList);

@SafeVarargs
static <E> E[] array(E... array)
{
    return array;
}

Что касается того, почему запрещен явный общий массив, это имеет какое-то отношение к стиранию типа. (То же самое беспокойство существует в указанном выше растворе, но подавляется @SafeVarargs) Однако это спорно; существуют различные способы решения этой проблемы, возможно, достаточно предупреждения о компиляторе. Но они решили прямо запретить это, возможно, потому что массивы уже не важны, поскольку у нас есть общие коллекции

Ответ 2

Я знаю, что относительно обходных решений этой проблемы Array.newInstance() - дорогой метод вызова. IIRC использует собственный метод для создания экземпляра массива, а также другое отражение. Я не могу предложить никаких статистических данных, но это кажется достаточно хорошей причиной для того, чтобы такая функциональность не была автоматически заменена компилятором, чтобы разрешить создание общего массива. Особенно учитывая существование ArrayList и т.д., Это просто не кажется насущной проблемой.

Ответ 3

Компиляторы могут самопроизвольно выполнять преобразование, они просто указываются не потому, что общие массивы не могут вести себя как не общие массивы.

См. 10.5. Исключение магазина Array:

Для массива, тип которого A[], где A является ссылочным типом, назначение компоненту массива проверяется во время выполнения, чтобы гарантировать назначение назначаемого значения компоненту.

Если тип назначаемого значения не совместим с совместимостью с типом компонента, создается ArrayStoreException.

Если тип компонента массива не был повторно идентифицирован, виртуальная машина Java не могла выполнить проверку хранилища, описанную в предыдущем абзаце. Вот почему выражение создания массива с невосстанавливаемым типом элемента запрещено.

A List<MyDTO>[] не будет бросать, если мы поместим в него какой-то другой List, поэтому он не будет вести себя как массив. Обратите внимание на последнее предложение из цитаты: "Вот почему выражение создания массива с невосстанавливаемым типом элемента запрещено". Именно по этой причине это указано. (И для записи это рассуждение всегда существовало, поэтому оно присутствовало, когда вопрос был опубликован в 2011 году.)

Мы все еще можем сделать это:

@SuppressWarnings({"unchecked","rawtypes"})
List<MyDTO>[] dtoLists = new List[] {
    new ArrayList<MyDTO>(), anExistingDtoList
};

Или это:

@SuppressWarnings("unchecked")
List<MyDTO>[] dtoLists = (List<MyDTO>[]) new List<?>[] {
    new ArrayList<MyDTO>(), anExistingDtoList
};

(Помимо статической проверки типов аргументов, свойство varargs эквивалентно: создает List[] и подавляет предупреждения.)

Теперь, конечно, спецификация может быть изменена на что-то вроде "Если тип назначаемого значения не совместим с совместимостью с необработанным типом типа компонента...", но в чем смысл? Это спасло бы несколько персонажей в некоторых необычных ситуациях, но в противном случае подавляло бы предупреждения для тех, кто не понимает последствий.

Кроме того, что учебник и другие типичные объяснения, которые я видел, не демонстрируют, так это то, как испечь систему ковариантной системы массивы.

Например, с учетом следующего объявления:

// (declaring our own because Arrays.fill is defined as
// void fill(Object[], Object)
// so the next examples would more obviously pass)
static <T> void fill(T[] arr, T elem) {
    Arrays.fill(arr, elem);
}

Знаете ли вы, что это компилируется?

// throws ArrayStoreException
fill(new String[1], new Integer(0));

И это тоже компилируется:

// doesn't throw ArrayStoreException
fill(dtoLists, new ArrayList<Float>());

До Java 8 мы могли бы выполнить эти вызовы fill, предоставив ему следующее объявление:

static <T, U extends T> void fill(T[] arr, U elem) {...}

Но это была только проблема с типом вывода, и теперь он работает "правильно", слепо помещая List<Float> в List<MyDTO>[].

Это называется загрязнением кучи. Это может привести к тому, что ClassCastException будет выпущено через некоторое время, вероятно, где-то полностью не связанное с действиями, которые фактически вызвали проблему. Загрязнение кучи общим контейнером, например List, требует более очевидных небезопасных действий, таких как raw типы, но здесь мы можем вызвать загрязнение кучи неявно и без каких-либо предупреждений.

Общие массивы (и, действительно, массивы в целом) дают нам статическую проверку в самых простых обстоятельствах.

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