Почему Set <? расширяет Foo <? >> разрешено, но Set <Foo <? >> не разрешено
Я хочу знать, как дженерики работают в такой ситуации и почему Set<? extends Foo<?>> set3 = set1;
Set<? extends Foo<?>> set3 = set1;
разрешено, но Set<Foo<?>> set2 = set1;
не является?
import java.util.HashSet;
import java.util.Set;
public class TestGenerics {
public static <T> void test() {
Set<T> set1 = new HashSet<>();
Set<?> set2 = set1; // OK
}
public static <T> void test2() {
Set<Foo<T>> set1 = new HashSet<>();
Set<Foo<?>> set2 = set1; // COMPILATION ERROR
Set<? extends Foo<?>> set3 = set1; // OK
}
}
class Foo<T> {}
Ответы
Ответ 1
Проще говоря, это потому, что Set<? extends Foo<?>>
Set<? extends Foo<?>>
является ковариантным (с ключевым словом extends
). Ковариантные типы доступны только для чтения, и компилятор откажется от любого действия записи, такого как Set.add(..)
.
Set<Foo<?>>
не является ковариантным. Он не блокирует действия записи или чтения.
Это...
Set<Foo<String>> set1 = new HashSet<>();
Set<Foo<?>> set2 = set1; // KO by compiler
... незаконно, потому что в противном случае я мог бы, например, поместить Foo<Integer>
в set1
через set2
.
set2.add(new Foo<Integer>()); // Whoopsie
Но...
Set<Foo<String>> set1 = new HashSet<>();
Set<? extends Foo<?>> set3 = set1; // OK
... является ковариантным (extends
ключевое слово), поэтому это законно. Например, компилятор откажется от операции записи, например set3.add(new Foo<Integer>())
, но примет операцию чтения, например set3.iterator()
.
Iterator<Foo<String>> fooIterator = set3.iterator(); // OK
set3.add(new Foo<String>()); // KO by compiler
Смотрите эти посты для лучшего объяснения:
Ответ 2
Возможно, проблема станет яснее, если вы укажете общий параметр Foo вне уравнения.
Рассматривать
final Set<Foo> set1 = new HashSet<>();
Set<Object> set2 = set1;
Это делает ошибку компиляции более очевидной. Если бы это было допустимо, было бы возможно вставить объект в set2, таким образом в set1, нарушая ограничение типа.
Set<? extends Foo> set3 = set1;
Это совершенно верно, потому что set1 также будет принимать типы, полученные из Foo.
Ответ 3
В дополнение к уже приведенным ответам я добавлю некоторые формальные объяснения.
Дано 4.10.2 (emp. Mine)
При заданном объявлении универсального типа C (n> 0) непосредственными супертипами параметризованного типа C, где Ti (1 ≤ я ≤ n) является типом, являются все следующие:
D <U1 θ,..., Uk θ>, где D - универсальный тип, который является прямым супертипом универсального типа C, а θ - замена [F1: = T1,..., Fn: = Tn].
C <S1,..., Sn>, где Si содержит Ti (1 ≤ я ≤ n) (§4.5.1).
Тип Object, если C является универсальным типом интерфейса без прямых суперинтерфейсов.
Сырой тип C.
Правило contains
указаны в 4.5.1:
Говорят, что аргумент типа T1 содержит другой аргумент типа T2, записанный как T2 <= T1, если набор типов, обозначаемых через T2, является доказуемо подмножеством набора типов, обозначаемых через T1 при рефлексивном и транзитивном замыкании следующих правил ( где <: обозначает подтип (§4.10)):
? расширяет T <=? расширяет S, если T <: S
? расширяет T <=?
? супер Т <=? супер S если S <: T
? супер Т <=?
? супер Т <=? расширяет объект
Т <= Т
Т <=? расширяет T
Т <=? супер т
Так как T <=? super T <=? extends Object =?
T <=? super T <=? extends Object =?
так что применение 4.10.2 Foo<T> <: Foo<?>
у нас есть ? extends Foo<T> <=? extends Foo<?>
? extends Foo<T> <=? extends Foo<?>
? extends Foo<T> <=? extends Foo<?>
. Но Foo<T> <=? extends Foo<T>
Foo<T> <=? extends Foo<T>
поэтому у нас есть Foo<T> <=? extends Foo<?>
Foo<T> <=? extends Foo<?>
.
Применяя 4.10.2, мы имеем этот Set<? extends Foo<?>>
Set<? extends Foo<?>>
является прямым супертипом Set<Foo<T>>
.
Формальный ответ на вопрос, почему ваш первый пример не компилируется, можно получить, предположив противоречие. Percisely:
Если Set<Foo<T>> <: Set<Foo<?>>
у нас есть тот Foo<T> <= Foo<?>
Который невозможно доказать, применяя рефлексивные или транзитивные отношения к правилам из 4.5.1.
Ответ 4
Я думаю, просто потому, что элемент Datatype элемента Set
отличается, хотя он должен быть таким же, за исключением Generic Datatype.
первый набор Set<Foo<T>>
тип данных Foo<T>
,
затем второй набор Set<Foo<?>>
равен Foo<?>
,
Как я вижу, тип данных элемента отличается от Foo<T> != Foo<?>
И не является универсальным типом, потому что он использует Foo
, поэтому может вызвать ошибку компиляции.
Это то же самое, что и ниже недопустимый пример другого типа данных:
Set<List<T>> set3 = new HashSet<>();
Set<List<?>> set4 = set3; // compilation error due to different element datatype List<T> != List<?>
Set<? extends Foo<?>> set3 = set1;
может потому что есть ? datatype
? datatype
который является универсальным и имеет цель, может принимать любой тип данных.
пример:
Set<List<T>> set4 = new HashSet<>();
Set<?> set5 = set4; // would be Ok