Javagenerics类型中的通配符参数在其范围内的正式条件是什么?
使用Java中的参数化类型,检查参数是否在其绑定工作中的规则如何完全针对通配符?
鉴于这样的类:
class Foo {}
试验编译器接受的内容可以了解:
- 一个
? extends
允许使用不相关的接口类型? extends
通配符:Foo
Foo
有效 - 一个
? extends
不允许使用不相关的类类型? extends
通配符:Foo
Foo
无效。 这是有道理的,因为没有类型可以是Number
和Thread
的子类型 - 在一个
? super
? super
通配符,通配符的下限必须是类型变量的边界的子类型:Foo
不允许使用Foo
因为Runnable
不是Number
的子类型。 同样,这种限制非常有意义。
但这些规则在哪里定义? 查看Java语言规范部分4.5 ,我没有看到任何区别于类的接口; 在应用我对JLS Foo
解释Foo
Foo
据说是有效的。 所以我可能误解了一些东西。 这是我的尝试:
从JLS的那一部分:
参数化类型由类或接口名称C和实际类型参数列表组成。 如果C不是generics类或接口的名称,或者实际类型参数列表中的类型参数的数量与C的已声明类型参数的数量不同,则是编译时错误。在下文中,每当我们说话时除了明确排除之外,我们还包括类或接口类型的通用版本。 在本节中,让A1,…,An成为C的forms类型参数,并且让Bi为AI的声明边界。 符号[Ai:= Ti]表示用类型Ti替换类型变量Ai,对于1 <= i <= n,并且在整个说明书中使用。
令P = G 为参数化类型。 必须是这样的情况,在P经历捕获转换(第5.1.10节)之后导致类型G ,对于每个实际类型参数Xi,1 <= i <= n, Xi <:Bi [A1:= X1,…,An:= Xn](§4.10),或发生编译时错误。
将其应用于P = Foo
Foo
:得到C = Foo
,n = 1,T1 = ? super Runnable
? super Runnable
和B1 = Number
。
对于捕获转换,此部分捕获转换定义适用:
如果Ti是表单的通配符类型参数? super Bi,则Si是一个新型变量,其上限为Ui [A1:= S1,…,An:= Sn],其下限为Bi。
这给出了G = Foo
其中X
是一个新的类型变量,上限为Number
,下限为Runnable
。 我没有看到任何明确禁止这种类型变量的东西。
B1 = Number
中没有类型变量,因此Bi [A1:= X1,…,An:= Xn]仍然只是Number
。 X
将Number
作为上限(来自捕获转换),并根据子类型规则 “类型变量的直接超类型是其绑定中列出的类型”,因此X
<: Number
(= Bi [A1:= X1] ,…,An:= Xn]),因此该参数在其范围内。 (但事实并非如此!)
按照相同的推理, 每个通配符都在其范围内,所以这里的某些东西是不对的……但是这个推理到底出错的地方呢? 如果正确应用,这些规则如何运作?
关于generics的JLS是不完整的,你抓住了另一个漏洞。 类型变量的下限几乎没有讨论过,我没有看到规格上的任何限制,无论是具有上限Number
X
还是下限的Runnable
。 他们可能把它排除在外。
直观地,必须至少有一种可能的类型满足类型变量的上限和下限,否则变量和使用该变量的所有类型都将是无用的。 由于这几乎肯定是编程错误,编译应该失败。
很容易检查上限和下限是否为空类型。 下界的所有超类型都是已知的; 至少其中一个应该是上限,否则没有类型在两个边界内。
–
两个Foo extends A>
Foo extends A>
案例在规范中有明确定义。 通过捕获转换,我们有新的类型变量X
,上限为A & Number
,规格表示上限为V1&...&Vm
如果对于任何两个类(不是接口)Vi和Vj,Vi不是Vj的子类,反之亦然,则是编译时错误。
因此,如果A = Thread,则捕获转换失败。