11

使用 Java 中的参数化类型,检查参数是否在其范围内的规则如何完全适用于通配符?

给定这样的类:

class Foo<T extends Number> {}

尝试编译器接受的内容会了解到:

  • ? extends允许使用不相关接口类型的通配符:Foo<? extends Runnable>有效
  • ? extends不允许使用不相关类类型的通配符:Foo<? extends Thread>无效。这是有道理的,因为没有类型可以是两者的子Number类型Thread
  • ? super通配符中,通配符的下限必须是类型变量边界的子类型:Foo<? super Runnable>不允许,因为Runnable不是 的子类型Number。同样,这个限制非常有意义。

但是这些规则是在哪里定义的?查看Java 语言规范第 4.5 节,我没有看到任何区分接口和类的东西。当应用我对 JLSFoo<? super Runnable>的解释时,据说是有效的。所以我可能误解了一些东西。这是我的尝试:

从 JLS 的那个部分:

参数化类型由类或接口名称 C 和实际类型参数列表 <T1 , ... , Tn> 组成。如果 C 不是泛型类或接口的名称,或者实际类型实参列表中的类型实参数量与 C 声明的类型实参数量不同,则属于编译时错误。在下文中,无论何时我们说对于类或接口类型,我们也包括泛型版本,除非明确排除。在本节中,让 A1 , ... , An 是 C 的形式类型参数,让 Bi 是 Ai 的声明边界。符号 [Ai := Ti] 表示将类型变量 Ai 替换为类型 Ti,用于 1 <= i <= n,并在整个规范中使用。

令 P = G<T1, ..., Tn> 为参数化类型。必须是这样的情况,在对每个实际类型参数 Xi, 1 <= i <= n 进行捕获转换(第 5.1.10 节)导致类型 G<X1, ..., Xn> 之后, Xi <: Bi[A1 := X1, ..., An := Xn] (§4.10),或发生编译时错误。

将其应用于 P = Foo<? super Runnable>:给出 C = Foo, n = 1, T1 =? super Runnable和 B1 = Number

对于捕获转换,这部分捕获转换的定义适用:

如果 Ti 是形式的通配符类型参数?super Bi,则 Si 是一个新类型变量,其上界为 Ui[A1 := S1, ..., An := Sn] ,下界为 Bi。

这给出了 G<X1, ..., Xn> =Foo<X>其中X是具有上限Number和下限的新类型变量Runnable。我没有看到任何明确禁止这种类型变量的东西。

B1 = 中没有类型变量Number,所以 Bi[A1 := X1, ..., An := Xn] 仍然是简单Number的 。 X具有Number作为上限(来自捕获转换),并且根据子类型化规则“类型变量的直接超类型是其边界中列出的类型”,所以X<: Number(= Bi[A1 := X1, ... , An := Xn]),所以这个参数在它的范围内。(但事实并非如此!)

遵循相同的推理,每个通配符都在其范围内,所以这里有些地方是不对的……但是这个推理到底哪里出错了?这些规则在正确应用时如何运作

4

1 回答 1

5

泛型上的 JLS 是不完整的,您在其中发现了另一个漏洞。几乎没有讨论类型变量的下限,而且我在规范中看不到对X上限Number和下限的任何限制Runnable。他们可能忽略了它。

直观地说,必须至少有一种可能的类型同时满足类型变量的上限和下限,否则该变量和使用该变量的所有类型将毫无用处。由于这几乎可以肯定是一个编程错误,编译应该会失败。

很容易检查上限和下限是否构成一组空类型。下界的所有超类型都是已知的;至少其中一个应该是上限,否则两个边界内都没有类型。

--

这两种Foo<? extends A>情况在规范中都有很好的定义。通过捕获转换,我们有一个X带有上限的新类型变量A & Number,并且规范说有一个上限V1&...&Vm

如果对于任何两个类(不是接口)Vi 和 Vj,Vi 不是 Vj 的子类,则这是一个编译时错误,反之亦然。

因此,如果 A=Thread,捕获转换失败。

于 2011-08-09T22:39:08.793 回答