Почему мы можем использовать массив с общей ссылкой

Отвечая на вопрос об этом здесь: https://stackoverflow.com/a/9872630/82609

Я попытался сделать следующее:

Comparator<String>[] comparators = new Comparator[] {...};

Оно работает! Но следующее не делает:

Comparator<String>[] comparators = new Comparator<String>[] {...};

По соответствующему вопросу я сделал предположение:

Я предполагаю, что изначально контракт массива может быть примерно таким:

Если вы создадите массив типа X, вы НИКОГДА не сможете поместить в него что-либо, что НЕ ЯВЛЯЕТСЯ X. Если вы попытаетесь, вы получите исключение ArrayStoreException.

Таким образом, разрешение массивов с созданием дженериков приведет к другому правилу, например:

Если вы создадите массив типа X<Y>, вы НИКОГДА не сможете поместить что-либо, что НЕ ЯВЛЯЕТСЯ X. Если вы попытаетесь, вы получите исключение ArrayStoreException. Но вы МОЖЕТЕ добавить объекты X<Y> и X<Z> из-за стирания типов!


Но, если подумать, не будет ли проблемой иметь:

Comparator<String>[] comparators = new Comparator<String>[] {...};

Я действительно не понимаю, почему это невозможно, поскольку использование такой вещи:

  • Проверьте классы, вставленные во время выполнения
  • Проверьте тип классов, вставленный во время компиляции

Наконец, мы можем использовать массив со ссылкой на общий тип, и из-за невозможности создать массив с общим типом, я думаю, многие люди даже не знают, что это возможно.

Мне просто интересно, знает ли кто-нибудь причину этого выбора?

Это как заставить людей использовать List<String> = new ArrayList(); вместо List<String> = new ArrayList<String>();.


dimitrisli, вы привели хороший пример из знаменитой книги Джошуа Блоха. Как вы объяснили, опасно использовать оба общих массива + ковариацию и может привести к ClassCastException, в то время как мы ожидаем ArrayStoreException от массива с использованием ковариации.

Но обратите внимание, что следующее все еще является законным и приводит к тому же:

List<String>[] stringLists = new List[1];
List<Integer> intList = Arrays.asList(42);
Object[] objects = stringLists;
objects[0] = intList;
String s = stringLists[0].get(0);

Однако он выдает предупреждение о непроверенном приведении во время компиляции и, как вы упомянули, ClassCastException во время выполнения.


person Sebastien Lorber    schedule 27.03.2012    source источник
comment
возможный дубликат Ошибка создания универсального массива   -  person Dave Webb    schedule 27.03.2012
comment
Кто-то уже задавал такой же вопрос. stackoverflow.com/questions/3903196/   -  person Dave Webb    schedule 27.03.2012
comment
@DaveWebb Я не думаю, что это совсем один и тот же вопрос - связанный вопрос касается ошибки компиляции и того, как ее обойти, в то время как это касается мотивирующей философии, лежащей в основе дизайна языка.   -  person Andrzej Doyle    schedule 27.03.2012
comment
Прочитайте первый ответ. Там есть ссылка на подробное объяснение того, что здесь происходит.   -  person Dave Webb    schedule 27.03.2012
comment
по этой ссылке есть несколько ответов на ваш вопрос ibm. com/developerworks/java/library/j-jtp01255/   -  person tsatiz    schedule 27.03.2012
comment
@DaveWebb, я согласен, в ссылке, о которой вы говорите, есть раздел об этом. Но я все же думаю, что вопросы разные. Это похоже на то, почему я не могу сделать List‹Animal› aList = new ArrayList‹Dog›() VS Как на самом деле работают дженерики   -  person Sebastien Lorber    schedule 27.03.2012


Ответы (2)


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

Как вы упомянули, стирание означает, что общие параметры недоступны во время выполнения, поэтому типы проверяются во время компиляции (будь то для List<String> или вашего Comparator<String>[]). Важно отметить, что это основано на общем параметре переменной.

Массивы, с другой стороны, проверяют типы своих аргументов во время выполнения, когда они вставляются, поэтому они могут выдать ArrayStoreException, если они используются неправильно (обычно из-за злоупотребления их ковариантностью). Таким образом, массивы должны иметь возможность выполнять обе ваши внутренние проверки маркеров, и, конечно же, они не могут проверять общий параметр во время выполнения. Следовательно, нет смысла создавать экземпляр универсального массива, так как массив должен будет полностью игнорировать универсальный параметр, что в лучшем случае может ввести в заблуждение.

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

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

person Andrzej Doyle    schedule 27.03.2012
comment
Спасибо, я думаю, вы указали на это: проблема, вероятно, в ковариации массива - person Sebastien Lorber; 27.03.2012

Цитата из великого Effective Java Second Edition стр. 120:

Почему создание универсального массива является незаконным — он не будет компилироваться!

List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)

Предположим, что строка 1, создающая общий массив, допустима. Строка 2 создает и инициализирует List<Integer>, содержащий один элемент. Строка 3 сохраняет массив List<String> в переменную массива Object, что допустимо, поскольку массивы являются ковариантными. Строка 4 сохраняет List<Integer> в единственный элемент массива Object, что успешно, потому что дженерики реализованы путем стирания: тип экземпляра List<Integer> во время выполнения — это просто List, а тип экземпляра List<String>[] во время выполнения — List[], так что это присваивание не имеет значения. т генерировать ArrayStoreException. Теперь мы в беде. Мы сохранили List<Integer> экземпляров в массиве, объявленном для хранения только List<String> экземпляров. В строке 5 мы извлекаем единственный элемент из единственного списка в этом массиве. Компилятор автоматически приводит полученный элемент к строке, но это целое число, поэтому во время выполнения мы получаем ClassCastException. Чтобы этого не произошло, строка 1 (которая создает универсальный массив) генерирует ошибку времени компиляции.

person dimitrisli    schedule 27.03.2012
comment
Спасибо, хороший пример, я отредактировал свой вопрос. Обратите внимание, что мы по-прежнему можем использовать List‹String›[] stringLists = new List[1]; вместо этого, и он работает нормально. Знаете, почему это разрешено? - person Sebastien Lorber; 27.03.2012
comment
Это предупреждение о безопасности типов, потому что мы пытаемся создать с помощью необработанной подписи List[] и присвоить ее общему List‹String›[]. В тот момент, когда мы меняем его на List‹String›[], компилятор жалуется, чтобы избежать описанной ситуации. - person dimitrisli; 27.03.2012