Как работают вложенные аргументы типа?
Почему декларация
Set<Set<String>> var = new HashSet<Set<String>>();
работает, но декларация
Set<Set<String>> var = new HashSet<HashSet<String>>();
Дроссель?
Я знаю, что "верхний уровень" (не уверен, что эта правильная фраза здесь) генерики в декларации играют по разным правилам, чем те, что находятся внутри заостренных скобок, но мне интересно узнать причину. Нелегкий вопрос для Google, поэтому я подумал, что попробую вас, ребята.
Ответы
Ответ 1
Это потому, что вы могли обойти систему типов, если бы это было разрешено. Желаемое свойство называется covariance. Если коллекции были ковариантными, вы могли бы это сделать:
Set<Set<String>> var = new HashSet<HashSet<String>>();
var.add(new TreeSet<String>());
TreeSet - это тип Set, поэтому статическая проверка типов не помешает вам вставить TreeSet в var. Но var ожидает только HashSets и HashSets, а не старый тип Set.
Лично я всегда пишу ваше первое объявление:
Set<Set<String>> var = new HashSet<Set<String>>();
Внешний класс должен иметь конкретную реализацию, но, как правило, нет необходимости прибивать внутренний класс к HashSet. Если вы создадите HashSet of Sets, вам хорошо идти. Если вы затем переходите к вставке серии HashSets в var, это ваш выбор позже в программе, но не нужно ограничивать объявление.
Для того, что стоит, массивы в Java являются ковариантными, в отличие от классов коллекции. Этот код будет компилироваться и будет генерировать исключение во время выполнения вместо того, чтобы быть пойманным во время компиляции.
// Arrays are covariant, assignment is permitted.
Object[] array = new String[] {"foo", "bar"};
// Runtime exception, can't convert Integer to String.
array[0] = new Integer(5);
Ответ 2
Причина в том, что Set<Set<String>>
не эквивалентен Set<HashSet<String>>
! A Set<Set<String>>
может содержать любой тип Set<String>
, тогда как a Set<HashSet<String>>
может содержать только HashSet<String>
.
Если Set<Set<String>> set = new HashSet<HashSet<String>>()
является законным, вы также можете сделать это без ошибок:
Set<HashSet<String>> setOfHashSets = new HashSet<HashSet<String>>();
Set<Set<String>> set = setOfHashSets;
set.add(new TreeSet<String>());
HashSet<String> wrong = set.iterator().next(); // ERROR!
Однако законным является использование ограниченного шаблона:
Set<? extends Set<String>> set = setOfHashSets;
Это разрешено, потому что теперь тип объекта, который содержит набор, это ? extends Set<String>
... другими словами, "определенный, но неизвестный класс, который реализует Set<String>
". Поскольку вы точно не знаете, какой конкретный тип Set<String>
разрешено содержать, вам не разрешено добавлять что-либо к нему (кроме null
)... возможно, вы ошибаетесь, что приводит к ошибке позже, как в моем первом примере.
Edit:
Обратите внимание на то, что общие типы "верхнего уровня", которые вы называете в своем вопросе, называются параметризованными типами, что означает типы, которые принимают один или несколько параметров типа. Причина, по которой Set<Set<String>> set = new HashSet<Set<String>>()
является законной, заключается в том, что HashSet<T>
реализует Set<T>
и поэтому является подтипом Set<T>
. Обратите внимание, однако, что параметр типа T
должен совпадать! Если у вас есть другой тип S
, который является подтипом T
, HashSet<S>
(или просто a Set<S>
четный) не является подтипом Set<T>
! Я объяснил причину этого выше.
Это именно та ситуация.
Set<Set<String>> set = new HashSet<Set<String>>();
Если мы заменим Set<String>
на T
здесь, получим Set<T> set = new HashSet<T>()
. Легко видеть, что фактические аргументы типа совпадают и что тип в правой части присваивания является подтипом типа слева.
Set<Set<String>> set = new HashSet<HashSet<String>>();
Здесь мы должны заменить Set<String>
и HashSet<String>
на T
и S
соответственно, где S
является подтипом T
. Сделав это, получим Set<T> set = new HashSet<S>()
. Как я уже говорил выше, HashSet<S>
не является подтипом Set<T>
, поэтому назначение является незаконным.
Ответ 3
Это из-за того, как дженерики работают на Java.
Set<? extends Set<String>> var = new HashSet<HashSet<String>>();
Ответ 4
Различие простое, разрешив Set<Set<String>> var = new HashSet<Set<String>>
разрешить var
принимать значения типа Set<String>
(из которых HashSet
имеет Set
).
Что касается Set<Set<String>> var = new HashSet<HashSet<String>>();
, он не только не будет компилироваться, потому что внутренний тип var
ожидает Set<String>
, но находит HashSet<String>
. Это означало бы, что var.add(new TreeSet<String>());
будет erronous (тип несовместимости между HashSet
и TreeSet
).
Надеюсь, что это поможет.
Ответ 5
Позволяет уменьшить ваш пример до более простого
//Array style valid an Integer is a Number
Number[] numArr = new Integer[10]
//Generic style invalid
List<Number> list1 = new ArrayList<Integer>();
//Compiled (erased) List valid, pre generics (java 1.4)
List list2 = new ArrayList();
Первая строка - это код с ковариантными массивами, который является юридическим кодом Java. Следующая строка содержит простой пример для вашей проблемы со списком целых чисел и номера, который является недопустимым. В последней строке у нас есть допустимые и стираемые не общие списки.
Давайте добавим элемент в наши числа, 1.5 кажется мне разумным числом ^^
//this will compile but give us a nice RuntimeException
numArr[0] = 1.5f
//This would compile and thanks to type erasure
//would even run without exception
list1.add(1.5f)
RuntimeExceptions не должно выполняться в действительном коде, но numArr может хранить только целые числа, а не числа, как можно было бы верить. Дженерики ловят эту ошибку во время компиляции, поскольку они не ковариантны.
И вот почему эти списки Number и Integer не могут быть приняты как одно и то же. Методы, предоставляемые обоими списками, принимают разные аргументы, список Integer более ограничен, только принимая целые числа. Это означает, что интерфейсы, предоставляемые обоими списками, несовместимы.
List<Number>.add(Number n)
List<Integer>.add(Integer n)
То же самое верно для ваших наборов
Set<Set<String>>.add(Set<String> s)
HashSet<HashSet<String>>.add(HashSet<String> s)
Добавление и другие методы обоих типов несовместимы.
(вторая попытка ответить, надеюсь, что на этот раз я не обращал внимания на кого-то)