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