Java规范中在哪里提到了List<T>赋值给List<? super T>?

16
假设类B继承自类A。下面的Java代码是合法的:
List<A> x;
List<? super B> y = x;

就规范而言,这意味着 List<A> 赋值给 List<? super B>。然而,我找不到规范中说这是合法的部分。特别是,我认为我们应该有子类型关系。

List<A>  <:  List<? super B>

但是Java 8规范的第4.10节将子类型关系定义为直接超类型关系“S >1 T”的传递闭包,并且它根据一个计算T的一组超类型的有限函数来定义直接超类型关系。由于可能存在任意数量从A继承的B,因此不存在一个有界函数,可以在输入List<A>时生成List<? super B>,因此规范的子类型定义似乎对超级通配符失效。4.10.2节“类和接口类型之间的子类型”确实提到了通配符,但它只处理通配符出现在潜在子类型中的另一个方向(这个方向适合于计算的直接超类型机制)。
问题:规范的哪一部分说明上述代码是合法的?
这是针对编译器代码的动机,所以仅凭直觉理解其合法性或想出处理它的算法是不够的。由于Java中的常规子类型问题是不可判定的,因此我希望能处理与规范完全相同的情况,因此需要处理此案例的规范部分。

1
定义“子类型关系”是什么?泛型不可具体化,所以我看不出你的观点。 - fge
不幸的是,我只能说子类型关系在规范的第4.10节中定义。 :) 从基本原理上定义并不能帮助解决问题,因为它无法确定规范的哪个部分说明了我的特定通配符assignsTo是有效的。我看不出规范的子类型关系如何处理它,但我也看不出规范的assignsTo关系如何处理它,所以我很困惑。 - Geoffrey Irving
澄清一下,我提到子类型的原因是assignsTo是根据扩展引用转换定义的,而扩展引用转换是根据子类型定义的。然而,assignsTo也是基于其他关系定义的,所以相关部分可能与子类型无关。 - Geoffrey Irving
1
我现在处于你几年前的同一境地。一切都是以直接超类型的术语定义的,或者至少希望是这样。正如@John Kugelman在下面指出的那样,在4.5.1的“包含”条款的语言中会变得有些奇怪。更奇怪的是,在与4.10.2结合使用时,它似乎是JLS中唯一一个将某个东西定义为其自身直接超类型的地方。更多信息请参见:https://dev59.com/Zan2oIgBc1ULPQZFEJrh - Laird Nelson
2个回答

11

List<? super B> 的定义是 §4.10.2. 类型之间的子类型关系List<A> 的超类型:

参数化类型 C<T1,...,Tn> 的直接超类型,其中 Ti (1 ≤ i ≤ n) 是一种类型,包括以下所有类型:

  • D<U1 θ,...,Uk θ>,其中 D<U1,...,Uk>C<T1,...,Tn> 的直接超类型,θ 是替换 [F1:=T1,...,Fn:=Tn]

  • C<S1,...,Sn>,其中 Si 包含 Ti (1 ≤ i ≤ n) (§4.5.1)。

C<T1,...,Tn> = List<A>C<S1,...,Sn> = List<? super B>

根据第二个要点,如果? super B 包含 A,则List<? super B>List<A>的超类型。

包含关系在§4.5.1. 类型参数和通配符中定义:

一个类型参数T1被认为包含另一个类型参数T2,写作T2 <= T1,如果在以下规则的自反传递闭包下,由T2表示的类型集合可以被证明是由T1表示的类型集合的子集(其中<:表示子类型关系(§4.10)):
  • 如果T <: S,则? extends T <= ? extends S

  • 如果S <: T,则? super T <= ? super S

  • T <= T

  • T <= ? extends T

  • T <= ? super T

根据第二个要点,我们可以看到? super B 包含 ? super A。根据最后一个要点,我们可以看到? super A 包含 A。因此,通过传递性,我们知道? super B 包含 A

我将这个社区百科变成了公共编辑,因为我不确定我是否掌握了所有细节。语言专家,请纠正我可能犯的任何错误。 - John Kugelman
“我不确定我是否完全理解了所有细节。” 你理解了。 - Radiodef
正如我在问题中提到的那样,4.10.2 指出要对作为子类型的事物应用捕获转换以查找其直接超类型。然而,在此情况下,子类型是 List<A>,因此没有捕获转换可应用。因此,我还不理解该部分在我的情况下如何工作。 - Geoffrey Irving
啊,我明白了。我错过了关键的 C<S1,...,Sn>, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1). 部分。谢谢! - Geoffrey Irving
1
@GeoffreyIrving 实际上,我想你是对的,? super A 在赋值语境中不会被捕获,但这并不重要。从4.10.2和4.5.1的摘录可以看出,List<? super A>List<A> 的直接超类型。 - Radiodef
完美,上次的编辑删除了让我误入歧途的部分。一切都好! - Geoffrey Irving

4

将列表分配给<?super B>实际上是什么意思?

考虑以下程序:

public class Generics {
    static class Quux { }
    static class Foo extends Quux { }
    static class Bar extends Foo { }

    public static void main(String... args) {
        List<Foo> fooList = new ArrayList<>();
        // This is legal Java
        List<? super Bar> superBarList = fooList;
        // So is this
        List<? super Foo> superFooList = fooList;

        // However, this is *not* legal Java
        superBarList.add(new Quux());

        // Neither is this
        superFooList.add(new Quux());

        // Or this:
        superFooList.add(new Object());

        // But this is fine
        superFooList.add(new Foo());
    }
}

为什么会这样呢?首先,让我们谈谈JLS说了些什么

来自JLS,§4.5.1:

如果类型参数T1在以下规则的自反和传递闭包下,其表示的类型集合可以被证明是类型参数T2表示的类型集合的子集,则称类型参数T1包含另一个类型参数T2,即T2 <= T1(其中<:表示子类型关系(§4.10)):

  • ? super T <= ? super S,如果S <: T
  • T <= ? super T

因此,如果S <: T,则T <= ? super S。


...但那是什么意思?

如果我不能添加一个new Quux()或者一个new Object()List<? super Foo>表示这个列表只包含严格的超类元素,比如Foo,但是我不知道它具体是哪种类型。换句话说,我可以声明这个列表为这样的类型,但是我不能向其中添加我不确定是否为? super Foo类型的元素。Quux可能是这种类型,但也可能不是。

因此,将List<Foo>分配给List<? super Bar>并不会导致堆污染,最终也不会成为问题。

更多阅读:AngelikaLanger的通用解释相关章节


2
这是一个很好的解释,我希望我的问题要么是关于直观理解,要么是关于安全性的语义证明。任何一种都会容易得多!:) 不幸的是,我陷入了编译器内部,并且特别需要在规范中为此情况定义assignsTo或subtype的位置。您绝对正确,它应该与类型参数上的包含关系有关,但不幸的是我无法弄清楚如何做到这一点。 - Geoffrey Irving

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