От эффективной Явы Джошуа Блох,
- Массивы отличаются от универсального типа двумя важными способами. Первые массивы ковариантны. Обобщения являются инвариантами.
Ковариант просто означает, что если X является подтипом Y, то X [] также будет подтипом Y []. Массивы являются ковариантными, так как string является подтипом Object So
String[] is subtype of Object[]
Инвариант просто означает, что независимо от того, является ли X подтипом Y или нет,
List<X> will not be subType of List<Y>.
Мой вопрос, почему решение сделать массивы ковариантными в Java? Существуют и другие сообщения SO, такие как Почему массивы инвариантны, а списки ковариантны? , но они, кажется, сосредоточены на Скале, и я не могу следовать.
java
arrays
generics
language-design
covariance
жаждующий знаний
источник
источник
LinkedList
.Ответы:
Через википедию :
Это отвечает на вопрос «Почему массивы ковариантны?» Или, точнее, «Почему массивы были ковариантными в то время ?»
Когда дженерики были введены, они были преднамеренно не сделаны ковариантными по причинам, указанным в этом ответе Джоном Скитом :
Первоначальная мотивация создания ковариантов массивов, описанная в статье в Википедии, не применима к генерикам, потому что подстановочные знаки сделали возможным выражение ковариации (и контравариантности), например:
источник
Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
ArrayStoreException
s по мере необходимости. Очевидно, что в то время это считалось достойным компромиссом. Сравните это с сегодняшним днем: многие рассматривают ковариацию массива как ошибку, оглядываясь назад.Причина в том, что каждый массив знает свой тип элемента во время выполнения, в то время как универсальная коллекция не знает из-за стирания типа.
Например:
Если это было разрешено с общими коллекциями:
Но это может вызвать проблемы позже, когда кто-то попытается получить доступ к списку:
источник
Может быть, это поможет: -
Обобщения не ковариантны
Массивы в языке Java являются ковариантными - это означает, что если Integer расширяет Number (что он и делает), то не только Integer также является Number, но Integer [] также является
Number[]
и вы можете передавать или назначатьInteger[]
где требуетсяNumber[]
. (Более формально, если Number является супертипом типа Integer, тоNumber[]
является супертипомInteger[]
.) Вы можете подумать, что то же самое верно и для универсальных типов -List<Number>
это супертипList<Integer>
, и вы можете передать,List<Integer>
гдеList<Number>
ожидается a . К сожалению, это не работает таким образом.Оказывается, есть веская причина, по которой это так не работает: это нарушило бы непатентованные средства обеспечения безопасности типов. Представьте, что вы можете присвоить
List<Integer>
aList<Number>
. Затем следующий код позволит вам поместить что-то, что не является целым числом, вList<Integer>
:Поскольку ln - это
List<Number>
, добавление Float к нему кажется совершенно законным. Но если у ln есть псевдонимli
, то это нарушит обещание безопасности типов, неявное в определении li, - это список целых чисел, поэтому универсальные типы не могут быть ковариантными.источник
ArrayStoreException
во время выполнения.WHY
том, что массивы сделаны ковариантными. как упомянул Sotirios, с Arrays можно получить ArrayStoreException во время выполнения, если Arrays были сделаны инвариантными, то мы могли бы обнаружить эту ошибку во время самой компиляции правильно?Animal
, который не должен принимать никакие элементы, полученные из других источников», от «Массива, который не должен содержать ничего, кромеAnimal
: и должен быть готов принять предоставленные извне ссылки наAnimal
. Код, который нуждается в первом, должен принимать массивCat
, а код, который нуждается во втором, не должен. Если компилятор может различать два типа, он может обеспечить проверку во время компиляции. К сожалению, единственное, что их отличает ...Массивы ковариантны по крайней мере по двум причинам:
Это полезно для коллекций, которые содержат информацию, которая никогда не изменится на ковариантную. Чтобы коллекция T была ковариантной, ее резервное хранилище также должно быть ковариантным. В то время как можно создать неизменную
T
коллекцию, которая не использовала бы вT[]
качестве своего резервного хранилища (например, используя дерево или связанный список), такая коллекция вряд ли будет работать так же, как и коллекция, поддерживаемая массивом. Кто-то может утверждать, что лучшим способом обеспечить ковариантные неизменяемые коллекции было бы определение типа «ковариантный неизменяемый массив», в котором они могли бы использовать резервное хранилище, но простое использование ковариации массива, вероятно, было проще.Массивы будут часто видоизменяться кодом, который не знает, какой тип объектов будет в них, но не будет помещать в массив то, что не было считано из этого же массива. Ярким примером этого является сортировка кода. Концептуально для типов массива могло бы быть возможным включить методы для обмена или перестановки элементов (такие методы могут быть в равной степени применимы к любому типу массива) или определить объект «манипулятор массива», который содержит ссылку на массив и одну или несколько вещей. он был прочитан из него и может включать методы для хранения ранее прочитанных элементов в массиве, из которого они получены. Если бы массивы не были ковариантными, пользовательский код не смог бы определить такой тип, но среда выполнения могла бы включать некоторые специализированные методы.
Тот факт, что массивы являются ковариантными, может рассматриваться как уродливый хак, но в большинстве случаев это облегчает создание рабочего кода.
источник
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
- Хороший вопросВажной особенностью параметрических типов является способность писать полиморфные алгоритмы, то есть алгоритмы, которые работают с структурой данных независимо от значения ее параметра, например
Arrays.sort()
.С обобщениями это делается с подстановочными типами:
Чтобы быть действительно полезными, подстановочные типы требуют подстановочного знака, а для этого требуется понятие параметра типа. Ничего из этого не было доступно в то время, когда массивы были добавлены в Java, а создание массивов ссылочного типа ковариантно позволило намного проще разрешить полиморфные алгоритмы:
Однако эта простота открыла лазейку в системе статического типа:
требует проверки во время выполнения каждого доступа на запись в массив ссылочного типа.
Короче говоря, новый подход, воплощенный в дженериках, делает систему типов более сложной, но также более статически безопасной, в то время как более старый подход был проще и менее статически безопасен. Разработчики языка выбрали более простой подход, имея более важные дела, чем закрытие небольшой лазейки в системе типов, которая редко вызывает проблемы. Позже, когда была создана Java и были решены насущные потребности, у них были ресурсы, чтобы сделать это правильно для обобщений (но изменение его для массивов могло бы сломать существующие программы Java).
источник
Обобщения являются инвариантами : из JSL 4.10 :
и несколько строк далее, JLS также объясняет, что
массивы ковариантны (первая буква):
4.10.3 Субтипирование среди типов массивов
источник
Я думаю, что они приняли неправильное решение в первую очередь, что сделало массив ковариантным. Это нарушает безопасность типов, как описано здесь, и они застряли с этим из-за обратной совместимости, и после этого они попытались не сделать ту же ошибку для универсального. И это одна из причин, по которой Джошуа Блох предпочитает списки массивам в пункте 25 книги «Эффективная Java (второе издание)»
источник
Мое предположение: когда код ожидает массив A [] и вы даете ему B [], где B является подклассом A, есть только две вещи, о которых нужно беспокоиться: что происходит, когда вы читаете элемент массива, и что происходит, если вы пишете Это. Поэтому нетрудно написать языковые правила, чтобы гарантировать, что безопасность типов сохраняется во всех случаях (главное правило состоит в том, что
ArrayStoreException
может быть выброшено, если вы попытаетесь вставить A в B []). Тем не менее, для универсального, когда вы объявляете классSomeClass<T>
,T
в теле класса может использоваться любое количество способов , и я предполагаю, что слишком сложно разработать все возможные комбинации, чтобы написать правила о том, когда вещи разрешены, а когда нет.источник