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

dac*_*cwe 8 java generics

有时我只是没有得到泛型.我经常在代码中使用最通用的集合版本.例如,如果我需要一套任何我会写的东西:

Set<?> set1 = new HashSet<Object>();
Run Code Online (Sandbox Code Playgroud)

编译器允许它,为什么不应该 - 它Set<?>是一般的Set<Object>(甚至更通用的..).但是,如果我使用"泛型的泛型"使它"更通用",那就不起作用:

Set<Class<?>> singletonSet = new HashSet<Class<Object>>(); // type mismatch
Run Code Online (Sandbox Code Playgroud)

到底是怎么回事?为什么是Set<Object>分配给Set<?>并且Set<Class<Object>>是不能分配给Set<Class<?>>


我总是找到解决这些问题的办法,但在这种情况下,我真的想知道为什么这是不允许的,而不是解决方法.

Mat*_*eid 5

泛型在Java中不协变.这个问题可以帮到你:

java泛型协方差

  • 这与协方差无关. (3认同)
  • @tibtof:它与协方差完全相关.A(`Class <Object>`)是B的子类型(`Class <?>`)但是`Set <A>`不是'Set <B>`的子类型,因为泛型不是协变的 (2认同)

Mic*_*uen 5

鉴于这种:

Set<?> set1 = new HashSet<Great>();
Run Code Online (Sandbox Code Playgroud)

如果您可视化无界通配符的含义,无界通配符上的时隙类型与之进行比较,则可行extends,因此如果您明确地这样做.

Set<? extends Object> set1 = new HashSet<Great>();
Run Code Online (Sandbox Code Playgroud)

要阅读,是伟大的扩展对象?是的,所以编译.

然后给出这个:

Set<Class<Great>> set3 = new HashSet<Class<Great>>();
Run Code Online (Sandbox Code Playgroud)

作为它的工作原理,如果你提取Set和HashSet的参数,那么Class<Great>这两个Class<Great>类型完全相同.

没有通配符的情况下,类型将直接进行比较,逐字逐句.

如果我们将编写set3,那么它接受协变类型(这个编译):

Set<? extends Class<Great>> set3a = new HashSet<Class<Great>>();
Run Code Online (Sandbox Code Playgroud)

要阅读,HashSet的Class<Great>类型兼容还是与Set的协变Class<Great>?是的.因此它编译.

虽然当然没有人会编写那种变量声明,如果它们只是完全相同的类型,那么它就是多余的.编译器正在使用通配符来确定赋值右侧的泛型的具体类或接口参数是否与左侧泛型的具体/接口兼容(理想情况是接口,如下所示).

List<? extends Set<Great>> b = new ArrayList<HashSet<Great>>();
Run Code Online (Sandbox Code Playgroud)

阅读它,是HashSet<Great>协变Set<Great>吗?是的.因此它编译


那么让我们回到你的代码场景:

Set<Class<?>> set3 = new HashSet<Class<Object>>();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,同样的规则适用,从内部开始读取它,是否与通配符兼容?是.之后你会转到下一个最外面的类型,它没有通配符.因此,在没有通配符的情况下,编译器将在Class<Object>和之间进行逐字检查Class<?>,这些检查不相等,因此编译错误.

如果它在最外面有通配符,那将编译.所以你可能意味着这个,这个编译:

Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();
Run Code Online (Sandbox Code Playgroud)

让我们做一个更有启发性的例子,让我们使用接口(Class是具体类型),比如Set.这编译:

List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();
Run Code Online (Sandbox Code Playgroud)

因此,从内到外阅读它以找出代码编译的原因,并明确地执行:

  1. 最内层:Object兼容? Extends Object吗?当然是啦.

  2. 最外层:HashSet<Object>兼容? extends Set<? extends Object>吗?当然是啦.

在数字1,这是(编译):

Set<? extends Object> hmm = new HashSet<Object>();
Run Code Online (Sandbox Code Playgroud)

在数字2,这是(编译):

List<? extends Set<? extends Object>> b = new ArrayList<HashSet<Object>>();
Run Code Online (Sandbox Code Playgroud)

现在让我们尝试删除最外面的通配符,编译器将不会进行类型兼容/协变检查,现在将逐字比较.

所以你现在知道下面的答案,这些会编译吗?

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

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

所以你猜它已经,正确...那将无法编译:-)

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

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

List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();
Run Code Online (Sandbox Code Playgroud)

然后,要更正您的代码,请执行以下任一操作:

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

Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();
Run Code Online (Sandbox Code Playgroud)