泛型的泛型和更多的通用<?>赋值

7
有时我就是搞不懂泛型。在代码中,我经常使用最通用的集合版本。例如,如果我需要一个任何类型的集合,我会写类似以下的代码:
Set<?> set1 = new HashSet<Object>();

编译器允许这种写法,为什么不允许呢——Set<?>Set<Object>一样通用(甚至更加通用)。但是,如果我使用“嵌套泛型”,使其更加通用就行不通了:

Set<Class<?>> singletonSet = new HashSet<Class<Object>>(); // type mismatch

发生了什么?为什么Set<Object>可以分配给Set<?>,而Set<Class<Object>>无法分配给Set<Class<?>>
我总是能找到解决这些问题的方法,但在这种情况下,我真的想知道为什么不允许这样做,而不是一个解决方法。

2
可能是 Java 泛型协变 的重复问题。 - Voo
@dacwe,非常抱歉打扰您了。您给了我一个很好的答案,但突然间您删除了帖子,我正要将其标记为已接受的答案。这是关于JNI问题的。我不知道如何联系您,所以在此发表评论。对造成的不便表示歉意-谢谢和问候-johnkrishna - johnkrishna
@dacwe 这是一个六天晚的回答 https://dev59.com/hGTWa4cB1Zd3GeqPBT4M#10823527 但它确实是一个合适的答案。 - Michael Buen
5个回答

6
鉴于此:

给定这个:

Set<?> set1 = new HashSet<Great>();

如果你能理解无界通配符的含义,那么这个方法就会起作用。无界通配符上的类型槽与extends进行比较,因此如果你明确地这样做。

Set<? extends Object> set1 = new HashSet<Great>();

阅读是扩展对象吗?是的,所以它可以编译。
然后给出以下内容:
Set<Class<Great>> set3 = new HashSet<Class<Great>>();

关于为什么能够工作,如果您提取Set和HashSet的参数,它们都是Class<Great>,这两个Class<Great>正好是相同类型。

在没有通配符的情况下,类型将直接逐字比较。

如果我们编写set3以接受协变类型(这将被编译):

Set<? extends Class<Great>> set3a = new HashSet<Class<Great>>();

阅读时,HashSet的Class<Great>类型是否与Set的Class<Great>协变或兼容?是的,它是。因此它可以编译。
当然,如果它们只是完全相同的类型,没有人会写那种变量声明,这是多余的。通配符被编译器用来确定右侧赋值的泛型的具体类或接口参数是否与左侧泛型的具体/接口(理想情况下是接口,如下所示)兼容。
List<? extends Set<Great>> b = new ArrayList<HashSet<Great>>();

阅读它,HashSet<Great>是否协变为Set<Great>?是的。因此它可以编译。
所以让我们回到你的代码场景:
Set<Class<?>> set3 = new HashSet<Class<Object>>();

在这种情况下,同样的规则适用,您需要从最内层开始阅读,Object 是否兼容于通配符?是的。然后,您转向下一个最外层类型,它没有通配符。因此,在没有通配符的情况下,编译器将在 Class<Object>Class<?> 之间进行逐字检查,它们不相等,因此会出现编译错误。
如果最外层有通配符,那么就可以编译。所以,您可能的意思是这个:这是可以编译的
Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();

让我们举一个更加生动的例子,使用接口(类是具体类型),比如 Set。这段代码可以编译通过:

List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();

请从内部向外部阅读,找出代码为什么能够编译,并进行显式操作:

  1. 最内层:对象是否兼容于 ? Extends Object?当然是。

  2. 最外层:是否兼容于 ? extends Set<? extends Object> ?当然是。

对于第一个问题,下面的代码可以编译:

Set<? extends Object> hmm = new HashSet<Object>();

在第二点中,这是指(编译):
List<? extends Set<? extends Object>> b = new ArrayList<HashSet<Object>>();

现在让我们试着去掉最外层通配符,编译器将不会进行类型兼容/协变检查,现在事情将会被直接比较。因此,你现在知道以下问题的答案,这些代码是否可以编译?
List<Set<?>> b = new ArrayList<HashSet<Object>>();

// this is same as above:
List<Set<? extends Object>> b = new ArrayList<HashSet<Object>>();

你已经猜到了,没错...它将无法编译 :-)

要纠正上述问题,请执行以下任一操作:

List<? extends Set<? extends Object>> b = new ArrayList<HashSet<Object>>();

List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();

然后,要纠正您的代码,请执行以下操作之一:
Set<? extends Class<? extends Object>> singletonSet = 
                                       new HashSet<Class<Object>>();

Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();

泛型相关的编译器错误终于有意义了。感谢您提供的精彩解释! - Somu

5

3
这与协方差无关。 - tibtof
2
@tibtof:这与协变性有关。(Class<Object>)是(Class<?>)的子类型,但Set<A>不是Set<B>的子类型,因为泛型不是协变的。 - newacct

0
我在jdk1.6.0_22下的eclipse中尝试了以下操作:
Set<Class<?>> singletonSet = new HashSet<Class<Object>>();

编译器给了我以下内容:
Type mismatch: cannot convert from HashSet<Class<Object>> to Set<Class<?>>

我把它改成这样就可以了:

HashSet<Class<Object>> singletonSet = new HashSet<Class<Object>>();

泛型的泛型似乎必须具有相同的集合类和被收集对象的类。

这似乎是Java中的一条规则,可能是因为运行时高错误率可能会导致严重崩溃。因此,以这种方式编写它也可以工作:

HashSet<Class<?>> singletonSet = new HashSet<Class<?>>();

这个代码可以运行,但是OP想要避免这种类型的代码。显然这段代码可以运行:HashSet<Class<Object>> singletonSet = new HashSet<Class<Object>>(); 他们是完全相同的类型。任何有纪律的程序员都会尝试实现“针对接口编程”,而不是直接使用具体类。 - Michael Buen

0
Set<Class<?>> 

这句话的意思是:任何类型的一组类

HashSet<Class<Object>>

这段话的意思是:一个HashSet的类必须是Object类型的-所以只有Object.class对象是被允许的-但这似乎没有太多意义,因为Object只有一个类对象。
我认为这个问题仍然有效,但也许你可以选择一个不同的示例来说明。

-1

这里的答案是它接受任何类型的对象。

因此,您必须编写以下内容

Set<Class<?>> val = new HashSet<Class<?>>();

在这里,您可以将任何类型的Object存储在Set中。


4
问题是为什么他上面的代码不起作用,而不是他如何修复它——这两件事情非常不同,前者要有趣得多。 - Voo
@tibtof,我刚刚给出了解决方案。 - Bhavik Ambani
1
我甚至不想要一个解决方案...我想知道为什么。但是我已经有一个可以接受的答案了,谢谢。 - dacwe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接