Как работают вложенные аргументы типа?

Почему декларация

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)

Добавление и другие методы обоих типов несовместимы.

(вторая попытка ответить, надеюсь, что на этот раз я не обращал внимания на кого-то)