泛型类型为Java程序引入了新的类型安全范围。 在同一类型上,泛型类型可以表现得很好,尤其是在使用通配符时 。 在本文中,我想解释子类型如何与Java泛型一起工作。

关于泛型类型子类型化的一般思考

不同泛型类型相同的类或接口的不定义亚型层级线性尽可能通用参数类型的子类型的层次结构。 例如,这意味着List <Number>不是List <Integer>的超类型。 下面的突出示例很好地说明了为什么禁止这种子类型化:

// assuming that such subtyping was possible
ArrayList<Number> list = new ArrayList<Integer>();
// the next line would cause a ClassCastException
// because Double is no subtype of Integer
list.add(new Double(.1d))

在进一步详细讨论之前,让我们首先考虑一下有关类型的一般信息:类型为程序引入了冗余。 当您将变量定义为Number类型时,请确保该变量仅引用知道如何处理Number定义的任何方法(例如Number.doubleValue)的对象。 这样,您可以确保可以安全地在变量当前表示的任何对象上调用doubleValue,并且不再需要跟踪变量引用的对象的实际类型。 (只要引用不为null。null引用实际上是Java严格类型安全性的少数例外之一。当然,null的“对象”不知道如何处理任何方法调用。)但是,如果您试图将String类型的对象分配给此Number类型的变量,Java编译器将认识到该对象实际上不理解Number所需的方法,并且会引发错误,因为它不能保证将来可能会调用例如doubleValue将被理解。 但是,如果我们缺少Java中的类型,则程序不会仅凭此更改其功能。 只要我们从不进行错误的方法调用,那么没有类型的Java程序就等效。 从这个角度来看,类型仅仅是为了防止我们的开发人员在愚蠢的事情上夺走一点自由。 此外,类型是隐式记录程序的一种好方法。 (诸如Smalltalk之类的其他编程语言不知道类型,并且除了在大多数时候困扰之外,这也有其好处。)

有了这个,让我们回到泛型。 通过定义通用类型,您可以允许通用类或接口的用户为其代码添加某种类型安全性,因为他们可以限制自己仅以某种方式使用您的类或接口。 例如,当您通过定义List <Number>将List定义为仅包含Numbers时,建议您每次尝试将String类型的对象添加到此列表中时,Java编译器都将引发错误。 在使用Java泛型之前,您只需要相信列表仅包含数字即可。 当您将集合的引用交给第三方代码中定义的方法或从该代码接收到集合时,这可能会特别痛苦。 使用泛型,即使在编译时,您也可以确保List中的所有元素都是某个超类型。

同时,通过使用泛型,您会泛型类或接口失去一些类型安全性。 例如,当您实现通用列表时

class MyList<T> extends ArrayList<T> { }

您不知道MyList中T的类型,并且必须期望该类型可以像Object一样简单。 这就是为什么您可以限制通用类型要求某些最小类型的原因:

class MyList<T extends Number> extends ArrayList<T> {double sum() { double sum = .0d;for(Number val : this) {sum += val.doubleValue();}return sum;}
}

这使您可以假定MyList中的任何对象都是Number的子类型。 这样,您就可以在泛型类中获得某种类型的安全性。

通配符

Java中的通配符等效于说出任何类型 。 因此,在实例化类型(即定义泛型类的某些实例应代表哪种具体类型)时,不允许使用通配符。 例如,在将对象实例化为新的ArrayList <Number>时发生类型实例化,其中您隐式调用包含在其类定义中的ArrayList的类型构造函数

class ArrayList<T> implements List<T> { ... }

ArrayList <T>是带有单个参数的简单类型构造函数。 因此,在ArrayList的类型构造函数定义(ArrayList <T>)中或在此构造函数的调用(新ArrayList <Number>)中,都不允许使用通配符。 但是,如果仅引用类型而不实例化新对象,则可以使用通配符,例如在局部变量中。 因此,允许以下定义:

ArrayList<?> list;

通过定义此变量,可以为任何通用类型的ArrayList创建占位符。 但是,由于对通用类型的这种限制很小,因此无法通过此变量对其的引用将对象添加到列表中。 这是因为您对变量列表所代表的泛型做出了这样的一般假设,即添加一个类型为String的对象并不安全,因为超出列表的列表可能需要某种其他任何子类型的对象。 通常,此必需的类型是未知的,并且不存在任何类型的子类型的对象,可以安全地添加该对象。 (例外是取消了类型检查的空引用。但是,您永远不应在集合中添加空值。)同时,从列表中删除的所有对象都将是对象类型,因为这是关于a的唯一安全假设此变量表示的所有可能列表的常见超类型 。 因此,您可以使用extends和super关键字形成更复杂的通配符:

ArrayList<?> list = new ArrayList<List<?>>();

在此示例中,由于不将通配符应用于类型实参,而不应用于构造的类型本身,因此满足了不得使用通配符类型构造ArrayList的要求。

至于泛型类的子类型化,我们可以总结一下,如果原始类型是子类型,并且泛型类型都是彼此的子类型,则某些泛型类型是另一种类型的子类型。 因此,我们可以定义

List<? extends Number> list = new ArrayList<Integer>();

因为原始类型ArrayList是List的子类型,并且因为泛型Integer是?的子类型? 扩展Number。

最后,请注意,通配符List <?>是List <?的快捷方式。 扩展Object>,因为这是一种常用的类型定义。 但是,如果泛型类型构造函数确实实施了另一个较低的类型边界,例如

class GenericClass<T extends Number> { }

变量GenericClass <?>而是GenericClass <?的快捷方式。 扩展Number>。

取放原则

这种观察将我们引到了“ 获取-放出”原理 。 另一个著名的例子可以最好地解释这一原理:

class CopyClass {<T> void copy(List<T> from, List<T> to) {for(T item : from) to.add(item);}
}

此方法定义不是很灵活。 如果您有一些列表List <Integer>,则无法将其内容复制到某些List <Number>甚至List <Object>。 因此,“获取和放置”原则规定,当您仅从通用实例(通过return参数)读取对象时,应始终使用下限通配符(?extends),而在以下情况下应始终使用上限通配符(?super)。您只提供通用实例方法的参数。 因此,更好的MyAddRemoveList实现如下所示:

class CopyClass {<T> void copy(List<? extends T> from, List<? super T> to) {for(T item : from) to.add(item);}
}

由于您仅从一个列表中读取内容,然后再写入另一个列表中,因此很遗憾,这是很容易被忽略的,您甚至可以在Java核心API中找到不采用“获取与放置”原理的类。 (请注意,上述方法还描述了泛型类型构造函数。)

请注意,类型List <? 扩展T>和List <? 超级T>都没有List <T>的要求那么具体。 还要注意,这种子类型对于非通用类型已经是隐式的。 如果定义的方法要求使用Number类型的方法参数,则可以自动接收任何子类型的实例,例如Integer。 但是,即使期望超型Number,也始终可以安全地读取您收到的此Integer对象。 而且由于无法回该引用,即您不能用Double的实例覆盖Integer对象,因此Java语言不需要通过声明方法签名(如void someMethod(<?扩展Number> number)。 同样,当您答应从方法中返回整数时,调用者只需要一个Number类型的对象,您仍然可以从方法中返回( )任何子类型。 同样,由于无法从假设的返回变量中读取值,因此在方法签名中声明返回类型时,不必通过通配符放弃这些假设的读取权限。

参考: 我的Java日常博客中来自我们JCG合作伙伴 Rafael Winterhalter的Java泛型子类型化 。

翻译自: https://www.javacodegeeks.com/2013/12/subtyping-in-java-generics.html

Java泛型中的子类型化相关推荐

  1. Java泛型中的PECS原则

    今天在写代码的时候使用到了这样一个方法签名: public void foo(Map<String, String> map); 在写这个参数的时候正好在想一些关于泛型的东西,于是: pu ...

  2. Java泛型中? 和 ? extends Object的异同分析

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 刘一手 来源 | 公众号「锅外的大佬」 Jav ...

  3. Java泛型中extends和super的理解(转)

    E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定 ...

  4. Java 泛型中的 ? T K V E等代表的意思

    Java泛型中的标记符含义: E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) T代表在调用时的指定类型 K - Key(键) V - Value(值 ...

  5. 聊一聊Java 泛型中的通配符 T,E,K,V,?

    点击上方"方志朋",选择"设为星标" 回复"1024"获取独家整理的学习资料 作者:glmapper juejin.im/post/5d57 ...

  6. 聊一聊-JAVA 泛型中的通配符 T,E,K,V,?

    前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据 ...

  7. Java泛型中extends T和super T的区别?

    <? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. ...

  8. java泛型中的标记,Java泛型中的标记符含义

    Java泛型中的标记符含义: E - Element (在集合中使用,因为集合中存放的是元素) T- Type(Java 类) K- Key(键) V- Value(值) N- Number(数值类型 ...

  9. JAVA 泛型中的通配符 T,E,K,V,?

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群",加入新技术 来源:8rr.co/2Xqx 前言 Java 泛型(generic ...

最新文章

  1. 我把 Spring Boot 的 banner 换成了美女,老板说工作不饱和,建议安排加班
  2. 怎么定义list_常用的List接口下集合
  3. breakout room at teams
  4. delphi程序设计之底层原理
  5. 开平推进智慧城市等领域信息化建设及公共数据资源共享
  6. 积木赛尔号机器人_《赛尔号大电影7》定档2019年暑期 十年陪伴升级归来
  7. HTML标签(持续更新)
  8. 解析xlsx与xls--使用2012poi.jar
  9. Fuel 9.0安装Openstack由于NTP检查没通过导致失败--解决办法
  10. centos 启动一个redis_linux环境下安装部署redis服务器
  11. 1.Sigar介绍和配置
  12. ES6阮一峰读书笔记第二章变量的解构赋值
  13. NPDP产品经理认证班将于近期开课
  14. android显示新界面,Android 12首个预览版发布 新界面新功能来袭
  15. 西电软工oop面向对象程序设计实验四上机报告
  16. 迷宫 动画 java_Java实现可视化迷宫
  17. React通过后台图片路径,打包下载图片
  18. 当别人加快脚步的时候,你更应该慢下来
  19. css 中的zoom,对CSS中zoom属性的总结
  20. MDS(multidimensional scaling)多维尺度分析

热门文章

  1. list 置顶元素_java集合指定元素排序:最前,按照提供的顺序排序?求算法
  2. python 列表生成表格_【转】Python 列表生成式
  3. 让CentOS能用yum自动安装rar和unrar
  4. aws eks_在生产中配置和使用AWS EKS
  5. jwt令牌_JWT令牌的秘密轮换
  6. gitlab10.x迁移_1.x到2.x的迁移:可观察与可观察:RxJava FAQ
  7. 模拟模型学习 几何布朗运动_Java的几何布朗运动
  8. lucene_Lucene组件概述
  9. oracle idm_批准Oracle IDM中的特定Web服务
  10. java分割句子_关于Java的一些句子