Подтвердить что ты не робот

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

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

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 перестанет быть счастливым. Ответы Массив общего списка не отвечайте на мой вопрос. Почему компиляторы не могут самопроизвольно выполнять преобразование?

4b9b3361

Ответ 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 типы, но здесь мы можем вызвать загрязнение кучи неявно и без каких-либо предупреждений.

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

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