Что-то не так с вложенной инициализацией ArrayList?
Рассмотрим следующие классы:
class A{ }
class B extends A{ }
Как мы знаем, это прекрасно компилируется:
List<? extends A> xx = new ArrayList<B>();
List<? extends List<? extends A>> xy = new ArrayList<List<? extends A>>();
Но это дает ошибку времени компиляции
List<? extends A> yx = new ArrayList<? extends A>();
List<? extends List<? extends A>> yy = new ArrayList<? extends List<? extends A>>();
Ошибка говорит:
обязательно: класс или интерфейс без границ
Мне известно, что свежие значения, интерпретируемые компилятором для вышеуказанных инициализаций, различны, и, следовательно, их нельзя безопасно привести. Но что означает "без границ" в приведенном выше сообщении об ошибке?
3 ответа
Эта ошибка относится к созданию нового ArrayList
чей прямой параметр типа верхнего уровня использует подстановочный знак. Это недопустимо, несмотря на то, что параметр вложенного типа может иметь подстановочный знак.
JLS, раздел 15.9, "Выражения создания экземпляров класса", гласит:
Если TypeArguments присутствует сразу после
new
или непосредственно перед(
тогда это ошибка времени компиляции, если какой-либо из аргументов типа является подстановочным знаком ( §4.5.1).
Ключевое слово здесь - "немедленно", потому что оно представляет аргумент прямого типа, а не аргумент вложенного типа.
Вот ограничение, упомянутое в статье Анжелики Лангер о дженериках и их использовании:
Их [подстановочные знаки] нельзя использовать для создания объектов или массивов, то есть создание экземпляра подстановочного знака не разрешено в новом выражении. Подстановочные знаки не являются типами, они являются заполнителями для члена семейства типов. В каком-то смысле создание шаблона с использованием подстановочных знаков аналогично интерфейсу: мы можем объявлять переменные типов интерфейса, но не можем создавать объекты типов интерфейса; созданные объекты должны быть класса, который реализует интерфейс. Аналогично экземплярам с подстановочными символами: мы можем объявлять переменные экземпляра с подстановочными символами, но не можем создавать объекты такого типа; созданные объекты должны иметь конкретный экземпляр из семейства экземпляров, обозначенных экземпляром с подстановочными знаками.
(акцент мой)
По сути, подстановочный тип не является конкретным типом и не может быть создан, с той же причиной, по которой нельзя создавать экземпляр интерфейса напрямую.
Однако это ничего не говорит о вложенных подстановочных знаках, которые связаны с созданием изначально не связанных объектов, которые в конечном итоге могут быть связаны с этим типом. В вашем примере это будет вложенным List
ы, которые могут быть добавлены к внешнему ArrayList
, Они могут быть ссылками на List
s любого подходящего подстановочного типа, но они здесь не создаются.
Резюме
Java не допускает подстановочный знак в выражении создания экземпляра (с new
), но он допускает использование вложенного подстановочного знака в таком выражении, поскольку прямой подстановочный тип не является конкретным типом.
Аргументы типа для создаваемого экземпляра класса не могут быть подстановочными знаками ( §15.9):
Если [аргументы типа] присутствуют сразу после
new
или непосредственно перед(
, тогда это ошибка времени компиляции, если любой из аргументов типа является подстановочным знаком.
Вот и все. Аргументы типа могут содержать символы подстановки (как в new ArrayList<List<?>>
), но подстановочные знаки не могут быть предоставлены напрямую (как в new ArrayList<?>
).
Это различие может быть объяснено грамматикой аргументов типа, на которые ссылается приведенная выше цитата ( §4.5.1):
TypeArguments:
< TypeArgumentList >
TypeArgumentList:
TypeArgument {, TypeArgument}
TypeArgument:
ReferenceType
Wildcard
Wildcard:
{Annotation} ? [WildcardBounds]
WildcardBounds:
extends ReferenceType
super ReferenceType
Другими словами, для каждого TypeArgument
в TypeArgumentList
предоставляется выражению создания экземпляра класса, TypeArgument
может быть только ReferenceType
и неWildcard
, Если ссылочный тип сам по себе является общим, то ему могут быть предоставлены аргументы типа, которые являются подстановочными знаками.
Во время создания / определения универсального типа тип должен быть указан. Обе эти
? extends A
? extends List<? exetnds A>
создаем новый подстановочный знак, который является еще неизвестным универсальным типом (вы можете узнать из?)
Обе эти
B
List<? extends A>
являются актуальными типами. Так что при звонке
new ArrayList<B>();
new ArrayList<List<? extends A>>();
Общий тип указан. В одном случае это B
в другой это List
содержащий ? extends A
,
При звонке
новый ArrayList(); новый ArrayList>();
Оба раза универсальный тип не определен. Это не разрешено
Причина по которой List<? extends A>
не вызывает ошибку, потому что здесь это тип. Список в вашем оригинале List<? extends List<? extends A>>
еще не был создан. Вы только определяете, что позже это должно быть List<? extends A>
, Если вы попытаетесь создать этот список, как этот
new ArrayList<? extends A>();
Вы столкнетесь с той же проблемой.
Понятный?