为什么我不信任通配符,以及为什么我们仍然需要通配符

在将子类型多态性(面向对象)与参数多态性(泛型)相结合的任何编程语言中,都会出现方差问题。 假设我有一个字符串列表,键入List<String> 。 我可以将其传递给接受List<Object>的函数吗? 让我们从这个定义开始:



interface List<T> {void add(T element);Iterator<T> iterator();...
}

破碎的协方差

凭直觉,我们可能首先认为应该允许这样做。 看起来不错:

void iterate(List<Object> list) {Iterator<Object> it = list.iterator();...
}
iterate(ArrayList<String>());

确实,包括Eiffel和Dart在内的某些语言确实接受此代码。 可悲的是,它是不完善的,如以下示例所示:

//Eiffel/Dart-like language with
//broken covariance:
void put(List<Object> list) {list.add(10);
}
put(ArrayList<String>());

在这里,我们将List<String>传递给接受List<Object>的函数,该函数尝试将Integer添加到列表中。

Java使用数组也会犯同样的错误。 以下代码编译:

//Java:
void put(Object[] list) {list[0]=10;
}
put(new String[1]);

它在运行时失败,并带有ArrayStoreException

使用地点差异

Java对于通用类和接口类型采用了不同的方法。 默认情况下,类或接口类型为invariant ,即:

  • 当且仅当UV完全相同类型时,才可将L<V>分配给L<V>

由于在很多时候这非常不方便,因此Java支持一种称为“ 使用站点差异”的方法 ,其中:

  • L<U>可分配给L<? extends V> 如果UV的子类型,则L<? extends V> ,并且
  • L<U>可分配给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的通配符表示法很丑陋,因此在本讨论中我们将不再使用它。 取而代之的是,我们将分别使用关键字inout来表示通变量和协方差。 从而:

  • L<out V>V协变的,并且
  • L<in V>是在逆变 V

给定的V称为通配符的边界

  • out V是一个上限通配符, V是其上限,并且
  • in V下界通配符, V是其下界。

从理论上讲,我们可以有一个具有上限和下限的通配符,例如L<out X in Y>
我们可以使用交集类型表示多个上限或多个下限,例如L<out U&V>L<in U&V>
请注意,类型表达式L<out Anything>L<in Nothing>指的是完全相同的类型,并且此类型是L的所有实例的超类型。 您会经常看到人们将通配符类型称为存在性类型 。 他们的意思是,如果我知道该list的类型为List<out Object>

List<out Object> list;

然后我知道存在一个未知的类型T ,这是Object的子类型,因此list的类型为List<T>
或者,我们可以从更宽泛的角度出发,说List<out Object>是所有List<T>类型的并集,其中TObject的子类型。
在具有使用地点差异的系统中,以下代码无法编译:

void iterate(List<Object> list) {Iterator<Object> it = list.iterator();...
}
iterate(ArrayList<String>()); //error: List<String> not a List<Object>

但是这段代码可以做到:

void iterate(List<out Object> list) {Iterator<out Object> it = list.iterator();...
}
iterate(ArrayList<String>());

正确地,此代码无法编译:

void put(List<out Object> list) {list.add(10); //error: Integer is not a Nothing
}
put(ArrayList<String>());

现在我们在兔子洞的入口。 为了将通配符类型集成到类型系统中,同时拒绝如上例所示的错误代码,我们需要一种更为复杂的类型参数替换算法。

会员输入使用地点差异

也就是说,当我们有一个泛型类型类似List<T>有一种方法void add(T element) ,而不是仅仅直截了当代ObjectT ,就像我们做普通不变的类型,我们需要考虑的方差类型参数出现的位置。 在这种情况下, T出现在List类型的反位置 ,即作为方法参数的类型。 我不会在这里写下的复杂算法告诉我们,在此位置我们应该用Nothing (底部类型)代替。
现在想象一下我们的List接口有一个带有以下签名的partition()方法:

interface List<T> {List<List<T>> partition(Integer length);...
}

List<out Y>partition()的返回类型是什么? 好吧,在不损失精度的情况下,它是:

List<in List<in Y out Nothing> out List<in Nothing out Y>>

哎哟。
由于没有人在他们的头脑中想去考虑这样的类型,因此明智的语言会抛弃其中的某些界限,留下这样的东西:

List<out List<out Y>>

这是可以接受的。 不幸的是,即使在这种非常简单的情况下,我们也已经远远超出了程序员可以轻松跟随类型检查器所做的工作的地步。
因此,这就是我不信任使用地点差异的原因所在:

  • Ceylon设计的一个重要原则是,程序员应始终能够重现编译器的推理。 这是原因的一些与使用现场方差出现的复杂类型的非常困难。
  • 它具有病毒性作用:一旦这些通配符类型在代码中立足,它们便开始传播,很难回到我的普通不变式类型。

申报地点差异

使用场所方差的一个更合理的选择是声明场所方差 ,在声明时我们指定泛型类型的方差。 这是我们在锡兰使用的系统。 在此系统下,我们需要将List分为三个接口:

interface List<out T> {Iterator<T> iterator();List<List<T>> partition(Integer length);...
}interface ListMutator<in T> {void add(T element);
}interface MutableList<T>satisfies List<T>&ListMutator<T> {}

List声明为协变类型, ListMutator为逆变类型, MutableList为两者的不变子类型。
似乎对多个接口的需求似乎是声明站点差异的一个很大的缺点,但事实证明,将变异与读取操作分开是很有用的,并且:

  • 变异运算通常是不变的,而
  • 读取操作通常是协变的。

现在我们可以这样编写函数:

void iterate(List<Object> list) {Iterator<Object> it = list.iterator();...
}
iterate(ArrayList<String>());void put(ListMutator<Integer> list) {list.add(10);
}
put(ArrayList<String>()); //error: List<String> is not a ListMutator<Integer>

您可以在此处阅读有关声明位置差异的更多信息。

为什么我们在锡兰需要使用场所差异

可悲的是,Java没有声明站点差异,并且与Java的干净互操作对我们来说很重要。 我不喜欢纯粹为了与Java互操作而在语言的类型系统中添加主要功能,因此多年来,我一直拒绝向Ceylon添加通配符。 最后,现实和实用性获胜,我的顽固失去了。 因此,锡兰1.1现在具有带有单界通配符的使用站点差异。
我试图尽可能严格地限制此功能,而仅提供体面的Java互操作所需的最低限度。 这意味着,就像在Java中一样:

  • 没有形式为List<in X out Y>双界通配符,并且
  • 通配符类型不能出现在类或接口定义的extendssatisfies子句中。

此外,与Java不同:

  • 没有隐含界的通配符,上限必须始终以显式形式编写,并且
  • 不支持通配符捕获

通配符捕获是Java的一个非常聪明的功能,它利用了通配符类型的“现有”解释。 给定这样的通用函数:

List<T> unmodifiableList<T>(List<T> list) => ... :

Java让我调用unmodifiableList() ,传递一个通配符类型,如List<out Object> ,返回另一个通配符List<out Object> ,理由是存在一些未知的X ,这是Object的子类型,对其进行调用是正确的。 也就是说,即使无法为任何TList<out Object>类型分配给List<T> ,此代码也被认为是类型正确的代码:

List<out Object> objects = .... ;
List<out 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362615.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL集群(PXC)入门

一、学习动机 伴随互联网行业的兴起&#xff0c;越来越多的领域需要相应的技术方案&#xff0c;比如&#xff1a;打出软件、电商平台、直播平台、电子支付、媒体社交。 身边常见的&#xff0c;校园出成绩那一年&#xff0c;我们会感觉网站异常的卡顿&#xff0c;因为访问人数太…

滚动条那些事

一、滚动条样式 1. ie8浏览器 名称描述scrollbar-arrow-color三角箭头的颜色scrollbar-face-color立体滚动条的颜色&#xff08;包括箭头部分的背景色&#xff09;scrollbar-3dlight-color立体滚动条亮边的颜色scrollbar-highlight-color滚动条的高亮颜色&#xff08;左阴影&…

【自定义组件】如何引用自定义组件

1. 可以在APP.JSON内引用自定义组件&#xff0c;此时该组件为所有页面共享。 2. 可以在页面的JSON文件内引用自定义组件&#xff0c;此时为该页面独享。 引入代码如下&#xff1a; /** * myTag 自定义组件名称 * path/to/the/custom/component 自定义组件所在路径 **/ {"…

Javascript高级程序设计第二版第十四章--异常--笔记

chaepter 14 错误异常分享。 其实主要是就是try{}catch(error){} finally{}这个语句的理解。主要一点&#xff1a;finally 在 return 之后 运行。这跟java里边的如出一辙。 比如&#xff1a;try{return1;}catch(error){return2;} finally{return0;}返回 return 0;然后接着就是 …

Java并发教程–原子性和竞争条件

原子性是多线程程序中的关键概念之一。 我们说一组动作是原子的&#xff0c;如果它们都以不可分割的方式作为一个单一的操作执行。 认为多线程程序中的一组操作将被串行执行是理所当然的&#xff0c;可能会导致错误的结果。 原因是由于线程干扰&#xff0c;这意味着如果两个线程…

sqlite3 数据库(一)

SQLite 数据库&#xff0c;是一个非常轻量级自包含(lightweight and self-contained)的DBMS&#xff0c;它可移植性好&#xff0c;很容易使用&#xff0c;很小&#xff0c;高效而且可靠。 SQLite嵌入到使用它的应用程序中&#xff0c;它们共用相同的进程空间&#xff0c;而不是…

HTML引入vue.js,在ie浏览器中不显示

因为只有两个页面&#xff0c;所以我没有用 vue-cli 搭框架&#xff0c;直接在 HTML 中引入vue.js 文件。发现其他浏览器都能正常显示&#xff0c;ie 下显示不正常&#xff0c;而且还报错&#xff0c;错误信息如下&#xff1a; 原因&#xff1a; 主要是因为 ie 不支持 ES6 的语…

【button】 按钮组件说明

原型&#xff1a; <buttonsize"[default | mini]"type"[primary | default | warn]"plain"[Boolean]"disabled"[Boolean]"loading"[Boolean]"form-type"[submit | reset]"open-type"[contact | share | g…

具有Infinispan的聚集幂等消费者模式

我创建了一个小项目 &#xff0c;该项目展示了如何将JBoss Infinispan与Apache Camel和幂等消费者模式一起使用&#xff0c;以确保消息不会在集群环境中被处理两次。 假设您有一个应用程序&#xff0c;该应用程序必须通过将其部署在多个容器上才能轻松扩展。 但是应用程序必须…

UVa OJ 128 - Software CRC (软件CRC)

Time limit: 3.000 seconds限时&#xff1a;3.000秒 Problem问题 You work for a company which uses lots of personal computers. Your boss, Dr Penny Pincher, has wanted to link the computers together for some time but has been unwilling to spend any money on the…

ipv4编址

IPv4的编址&#xff1a; IPv4的地址有32位&#xff0c;通过使用点分十进制法&#xff0c;将其划分成4个由“.”隔断的部分&#xff0c;每一个部分的取值是0~255 {2^0~(2^8)-1} IP地址是32位类似这样的二进制串&#xff1a;1100 0000 1111 1111 1111 1111 1111 1110&#xff08;…

基于 vue 的验证码组件

登录页面有个验证码&#xff0c;暂时没用到后台&#xff0c;在网上找了两个博客&#xff0c;记录一下。 一、直接写&#xff08;参考-UIEngineer&#xff09; 这个样式比较简单&#xff0c;直接在需要验证码的地方添加就行了。如果这个页面比较复杂&#xff0c;用组件会比较好…

Java 8 Friday:更多功能关系转换

过去&#xff0c;我们一直在每个星期五为您提供有关Java 8的新内容的新文章。这是一个非常令人兴奋的博客系列 &#xff0c;但我们想再次将重点放在Java和SQL的核心内容上。 我们仍然偶尔会写关于Java 8的博客&#xff0c;但不再是每个星期五&#xff08;有些人已经注意到&…

【WXS全局对象】Date

属性&#xff1a; 名称说明Date.parse( [dateString] )解析一个日期时间字符串&#xff0c;并返回 1970/1/1 午夜距离该日期时间的毫秒数。Date.UTC(year,month,day,hours,minutes,seconds,ms) 根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。 参数&#xff1a;year/m…

13个不可不知的ASP.NET MVC扩展点

ASP.NET MVC设计的主要原则之一是可扩展性。处理管线&#xff08;processing pipeline&#xff09;上的所有&#xff08;或大多数&#xff09;东西都是可替换的。因此&#xff0c;如果您不喜欢ASP.NET MVC所使用的约定&#xff08;或缺乏某些约定&#xff09;&#xff0c;您可以…

程序员常用的3大Web安全漏洞防御解决方案:XSS、CSRF及SQL注入(图文详解)

https://blog.csdn.net/ChenRui_yz/article/details/86489067 随着互联网的普及&#xff0c;网络安全变得越来越重要&#xff0c;程序员需要掌握最基本的web安全防范&#xff0c;下面列举一些常见的安全漏洞和对应的防御措施。01 常见的Web安全问题1.前端安全XSS 漏洞CSRF 漏洞…

在 HTML 中引入 vue.js 写页面

突然说要写两个页面&#xff08;只有两个页面&#xff0c;不是一个完整的项目。。&#xff09;&#xff0c;有点懵&#xff0c;不知道从哪下手&#xff0c;而且只对 vue 熟悉那么一丢丢&#xff0c;其他框架不是很熟悉。但是没办法鸭&#xff0c;只能硬着头皮去做了&#xff01…

JavaFX技巧14:StackPane子项-隐藏但不消失

另一个简短提示&#xff1a;Swing提供了一个名为CardLayout的布局管理器&#xff0c;该管理器管理容器内的一组组件&#xff08;卡&#xff09;&#xff0c;但始终仅显示其中一个。 方法CardLayout.show&#xff08;Container &#xff0c;String&#xff09;允许在组件/卡之间…

【WXS数据类型】Array

属性&#xff1a; 名称值类型说明[Array].constructor[String]返回值为“Array”,表示类型的结构字符串[Array].length[Number]返回数组长度 方法&#xff1a; 原型&#xff1a;[Array].toString() 说明&#xff1a;将数组转换成字符串&#xff0c;用逗号分隔每个元素 原型&am…

Mschart图表制作

首先一次安装这三个 &#xff08;1&#xff09;.Microsoft .NET Framework 3.5 的 Microsoft 图表控件 &#xff08;2&#xff09;.Microsoft .NET Framework 3.5 语言包的 Microsoft 图表控件 &#xff08;3&#xff09;.Microsoft Chart Controls Add-on for Microsoft Visua…