汕头网站建设培训公司,衣服货源怎么找厂家拿,百度搜不到WordPress文章,全网热度指数限定通配符和非限定通配符在将子类型多态性#xff08;面向对象#xff09;与参数多态性#xff08;泛型#xff09;相结合的任何编程语言中#xff0c;都会出现方差问题。 假设我有一个字符串列表#xff0c;键入ListString 。 我可以将其传递给接受ListObj… 限定通配符和非限定通配符 在将子类型多态性面向对象与参数多态性泛型相结合的任何编程语言中都会出现方差问题。 假设我有一个字符串列表键入ListString 。 我可以将其传递给接受ListObject的函数吗 让我们从这个定义开始 interface ListT {void add(T element);IteratorT iterator();...
}破碎的协方差 凭直觉我们可能首先认为应该允许这样做。 看起来不错 void iterate(ListObject list) {IteratorObject it list.iterator();...
}
iterate(ArrayListString()); 确实包括Eiffel和Dart在内的某些语言确实接受此代码。 可悲的是它是不完善的如以下示例所示 //Eiffel/Dart-like language with
//broken covariance:
void put(ListObject list) {list.add(10);
}
put(ArrayListString()); 在这里我们将ListString传递给接受ListObject的函数该函数尝试将Integer添加到列表中。 Java对数组也会犯同样的错误。 以下代码编译 //Java:
void put(Object[] list) {list[0]10;
}
put(new String[1]); 它在运行时失败并带有ArrayStoreException 。 使用地点差异 但是对于通用类和接口类型Java采用了不同的方法。 默认情况下类或接口类型为invariant 即 当且仅当U与V类型完全相同时才可将LV分配给LV 。 由于这在很多时候非常不方便因此Java支持一种称为use-sitevariance的方法 其中 LU可分配给L? extends V 如果U是V的子类型则L? extends V 并且 LU可分配给L? super V L? super V如果U是的超类型V 。 丑陋的语法? extends V ? extends V或? super V ? super V称为通配符 。 我们还说 L? extends V L? extends V在V是协变的并且 L? super V L? super V在V是反变的。 由于Java的通配符表示法非常丑陋因此在本讨论中我们将不再使用它。 取而代之的是我们将分别使用关键字in和out来表示通变量和协方差。 从而 Lout V在V是协变的并且 Lin V是在逆变 V 。 给定的V称为通配符的边界 out V是一个上限通配符 V是其上限并且 in V是下界通配符 V是其下界。 从理论上讲我们可以有一个具有上限和下限的通配符例如Lout X in Y 。 我们可以使用交集类型表示多个上限或多个下限例如Lout UV或Lin UV 。 请注意类型表达式Lout Anything和Lin Nothing指的是完全相同的类型并且此类型是L的所有实例的超类型。 您会经常看到人们将通配符类型称为存在性类型 。 他们的意思是如果我知道list的类型为Listout Object Listout Object list; 然后我知道存在一个未知的类型T 这是Object的子类型因此list的类型为ListT 。 或者我们可以从更宽泛的角度出发说Listout Object是所有ListT类型的并集其中T是Object的子类型。 在具有使用地点差异的系统中以下代码无法编译 void iterate(ListObject list) {IteratorObject it list.iterator();...
}
iterate(ArrayListString()); //error: ListString not a ListObject 但是这段代码可以做到 void iterate(Listout Object list) {Iteratorout Object it list.iterator();...
}
iterate(ArrayListString()); 正确地此代码无法编译 void put(Listout Object list) {list.add(10); //error: Integer is not a Nothing
}
put(ArrayListString()); 现在我们在兔子洞的入口。 为了将通配符类型集成到类型系统中同时像上面的示例一样拒绝不正确的代码我们需要一种更为复杂的算法来替换类型实参。 会员输入使用地点差异 也就是说当我们有一个泛型类型类似ListT有一种方法void add(T element) 而不是仅仅直截了当代Object的T 就像我们做普通不变的类型我们需要考虑的方差类型参数出现的位置。 在这种情况下 T出现在List类型的反位置 即作为方法参数的类型。 我不会在这里写下的复杂算法告诉我们在此位置我们应该用Nothing 底部类型代替。 现在想象一下我们的List接口有一个带有以下签名的partition()方法 interface ListT {ListListT partition(Integer length);...
} Listout Y的partition()的返回类型是什么 好吧在不损失精度的情况下它是 Listin Listin Y out Nothing out Listin Nothing out Y 哎哟。 由于没有人在他们的头脑中想去考虑这样的类型因此明智的语言会抛弃其中的一些界限而留下这样的东西 Listout Listout Y 这是可以接受的。 不幸的是即使在这种非常简单的情况下我们也已经远远超出了程序员可以轻松跟随类型检查器所做的工作的地步。 因此这就是我不信任使用地点差异的原因所在 Ceylon设计的一个重要原则是程序员应始终能够重现编译器的推理。 这是原因的一些与使用现场方差出现的复杂类型的非常困难。 它具有病毒性作用一旦这些通配符类型在代码中立足它们便开始传播很难回到我的普通不变式类型。 申报地点差异 使用场所方差的一种更合理的选择是声明场所方差 在声明时我们指定泛型类型的方差。 这是我们在锡兰使用的系统。 在此系统下我们需要将List分为三个接口 interface Listout T {IteratorT iterator();ListListT partition(Integer length);...
}interface ListMutatorin T {void add(T element);
}interface MutableListTsatisfies ListTListMutatorT {} List声明为协变类型 ListMutator声明为协变类型 ListMutator声明为MutableList的不变子类型。 似乎对多个接口的需求似乎是声明站点差异的一个很大的缺点但事实证明将突变与读取操作分开是很有用的并且 变异运算通常是不变的而 读取操作通常是协变的。 现在我们可以这样编写函数 void iterate(ListObject list) {IteratorObject it list.iterator();...
}
iterate(ArrayListString());void put(ListMutatorInteger list) {list.add(10);
}
put(ArrayListString()); //error: ListString is not a ListMutatorInteger 您可以在此处阅读有关声明位置差异的更多信息。 为什么我们在锡兰需要使用场所差异 可悲的是Java没有声明站点差异并且与Java的良好互操作对我们来说非常重要。 我不喜欢纯粹为了与Java互操作而在语言的类型系统中添加主要功能因此多年来我一直拒绝向Ceylon添加通配符。 最后现实和实用性获胜而我的顽固失去了。 因此Ceylon 1.1现在具有带有单界通配符的使用站点差异。 我试图尽可能严格地限制此功能仅提供体面的Java互操作所需的最低限度的功能。 这意味着就像在Java中一样 没有形式为Listin X out Y双界通配符并且 在类或接口定义的extends或satisfies子句中不能出现通配符类型。 此外与Java不同 没有隐式界通配符上限必须始终以显式形式编写并且 不支持通配符捕获 。 通配符捕获是Java的一个非常聪明的功能它利用了通配符类型的“现有”解释。 给定这样的通用函数 ListT unmodifiableListT(ListT list) ... : Java让我调用unmodifiableList() 传递一个通配符类型如Listout Object 返回另一个通配符Listout Object 原因是存在一些未知的X 这是Object的子类型对其进行调用是正确的。 也就是说即使无法为任何T将Listout Object类型分配给ListT 也认为该代码是正确的 Listout Object objects .... ;
Listout Object unmodifiable unmodifiableList(objects); 在Java中涉及通配符捕获的键入错误几乎是无法理解的因为它们涉及未知且难以理解的类型。 我没有计划向锡兰添加对通配符捕获的支持。 试试看 使用站点差异已经实现并且已经在Ceylon 1.1中起作用如果您非常有动力可以从GitHub获得。 即使此功能的主要动机是出色的Java互操作性但在其他情况可能很少见中通配符将很有用。 但是这并不表示我们的方法有任何重大变化。 除极端情况外我们将继续在Ceylon SDK中使用声明站点差异。 更新 我只是意识到我忘了感谢Ross Tate的帮助帮助我更好地了解了成员打字算法中使用地点差异的问题。 罗斯知道这些非常棘手的东西 翻译自: https://www.javacodegeeks.com/2014/08/why-i-distrust-wildcards-and-why-we-need-them-anyway.html限定通配符和非限定通配符