为什么声明
Set<Set<String>> var = new HashSet<Set<String>>();
工作但声明
Set<Set<String>> var = new HashSet<HashSet<String>>();
扼流圈?
我知道“顶级”(不确定这里是否是正确的短语)声明中的泛型通过与尖括号内的规则不同的规则,但我有兴趣了解原因。谷歌不是一个简单的问题,所以我想我会试试你们。
答案 0 :(得分:12)
这是因为如果允许的话,你可以绕过类型系统。您想要的属性称为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>>();
外部类需要有一个conrete实现,但通常没有必要专门确定内部类到HashSet。如果你创建了一个HashSet of Sets,你就可以了。您是否继续在var中插入一系列HashSet是您在程序后期的选择,但无需限制声明。
对于它的价值,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);
答案 1 :(得分:7)
原因是Set<Set<String>>
不等同于Set<HashSet<String>>
! Set<Set<String>>
可能包含任何类型的Set<String>
,而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
除外)...您可能错了,稍后会像我的第一个例子中那样导致错误。
修改强>
请注意,您在问题中引用的“顶级”泛型称为参数化类型,表示采用一个或多个类型参数的类型。 Set<Set<String>> set = new HashSet<Set<String>>()
合法的原因是HashSet<T>
实现了Set<T>
,因此是Set<T>
的子类型。但请注意,类型参数T
必须匹配!如果您的其他类型S
是T
的子类型,则HashSet<S>
(或仅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>
的子类型,因此分配是非法的。
答案 2 :(得分:3)
这是因为泛型在Java中的工作方式。
Set<? extends Set<String>> var = new HashSet<HashSet<String>>();
答案 3 :(得分:0)
区别很简单,允许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>());
会出现错误(HashSet
和TreeSet
之间的类型不兼容)。
希望这有帮助。
答案 4 :(得分:0)
让我们将您的示例简化为更简单的
//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代码。下一行包含一个简单的示例,用于解决您的问题,包括整数列表和无效的数字。在最后一行中,我们有有效和已删除的非通用列表。
让我们为Numbers添加一个项目,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列表不能被接受的原因。两个列表提供的方法接受不同的参数,整数列表更受限于仅接受整数。这意味着两个列表提供的接口都不兼容。
List<Number>.add(Number n)
List<Integer>.add(Integer n)
同样适用于你的套装
Set<Set<String>>.add(Set<String> s)
HashSet<HashSet<String>>.add(HashSet<String> s)
两种类型的add和其他方法都不兼容。
(第二次尝试回答,希望我这次不介意某人)