前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java泛型中<?> 和 <? extends Object>的异同分析

Java泛型中<?> 和 <? extends Object>的异同分析

作者头像
程序猿DD
发布于 2023-04-17 07:40:56
发布于 2023-04-17 07:40:56
79601
代码可运行
举报
文章被收录于专栏:程序猿DD程序猿DD
运行总次数:1
代码可运行

点击上方蓝色“程序猿DD”,选择“设为星标”

回复“资源”获取独家整理的学习资料!

作者 | 刘一手

来源 | 公众号「锅外的大佬」

Java Generics – <?> vs <? extends Object>

相信很多人和我一样,接触Java多年,却仍旧搞不清楚 Java 泛型中 <?>和 <? extends Object>的相似和不同。但是,这应该是一个比较高端大气上档次的Question, 在我们进行深入的探讨之前,有必要对Java泛型有一个基础的了解。如果还不了解的,请看上一篇文章!

上一篇文章地址:(http://mp.weixin.qq.com/s?__biz=MzIzNzYxNDYzNw==&mid=2247484950&idx=1&sn=688f958d6f343ee2e314a724ee1129a3&chksm=e8c4a554dfb32c421bb1ac3e0fd461d997d82b68ffc8ea41367a71d537df4a7455af5810e3f4&scene=21#wechat_redirect)

1. 泛型产生的背景

在 JDK5 中引入了泛型来消除编译时错误和加强类型安全性。这种额外的类型安全性消除了某些用例中的强制转换,并使程序员能够编写泛型算法,这两种方法都可以生成更具可读性的代码。

例如,在 JDK5 之前,我们必须使用强制转换来处理列表的元素。这反过来又产生了一类特定的运行时错误:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List aList = new ArrayList();
aList.add(new Integer(1));
aList.add("a_string");
        
for (int i = 0; i < aList.size(); i++) {
    Integer x = (Integer) aList.get(i);
}

现在,我们想解决两个问题:

  • 我们需要一个显式转换来从 aList 中提取值——类型取决于左侧的变量类型(在本例中为Integer
  • 当我们试图将 a_string 转换为 Integer 时,在第二次迭代中会出现运行时错误。

泛型填补了这个空白,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Integer> iList = new ArrayList<>();
iList.add(1);
iList.add("a_string"); // compile time error
 
for (int i = 0; i < iList.size(); i++) {
    int x = iList.get(i);
}

执行上述代码,编译器会告诉我们,无法将 a_string 添加到 Integer 类型的 List 中,这比起在运行时才发现异常要好很多。而且,不需要显式转换,因为编译器已经知道 iList 包含 Integer类型的数据。另外,由于自动拆箱的关系,我们甚至不需要使用 Integer 类型,它的原始类型就足够了。

2. 泛型中的通配符

问号或通配符在泛型中用来表示未知类型。它可以有三种形式:

  • 无界通配符:List<?> 表示未知类型的列表
  • 上界通配符:List<? extends Number> 表示 Number 或其子类型(如IntegerDouble)的列表
  • 下界通配符:List<? super Integer> 表示Integer或其超类型NumberObject的列表

由于 Object 是 Java 中所有类型的固有超类,所以我们会认为它也可以表示未知类型。换句话说,List<?> 和List<Object> 可以达到相同的目的。但事实并非如此。

来看看这两个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void printListObject(List<Object> list) {    
    for (Object element : list) {        
        System.out.print(element + " ");    
    }        
}    
 
public static void printListWildCard(List<?> list) {    
    for (Object element: list) {        
        System.out.print(element + " ");    
    }     
}

给出一个整数的列表,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Integer> li = Arrays.asList(1, 2, 3);

执行 printListObject(li) 不会编译,并且我们将得到以下错误:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
The method printListObject(List<Object>) is not applicable for the arguments (List<Integer>)

而执行 printListWildCard(li) 将通过编译,并将 1 2 3 输出到控制台。

3. <?>和<? extends Object>的相同之处

在上面的示例中,如果我们将 printListWildCard 方法更改为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void printListWildCard(List<? extends Object> list)

它的工作方式与 printListWildCard(List<?>)相同。这是因为 Object 是 Java 所有对象的超类,基本上所有的东西都扩展了Object。因此,这个方法也会处理一个 Integer 类型的List。

也就是说, <?> 和 <? extends Object> 在这个例子中是同一个意思。

虽然在大多数情况下,这是正确的,但也有一些区别。接下来我们就来看看它们之间的差异。

4. <?>和<? extends Object>的不同之处

可重构类型是指那些在编译时未被擦除的类型。换句话说,一个不可重构类型,运行时将比编译时表达的信息更少,因为其中一些信息会被擦除。

一般来说,参数化类型是不可重新定义的。比如 List<String> 和 Map<Integer,String> 就不可重新定义。编译器会擦除它们的类型,并将它们分别视为列表和映射。

这个准则的唯一例外是无界通配符类型。也就是说, List<?> 以及 Map<?, ?> 是可重写的。

另外,List<? extends Object> 不可重写。虽然微妙,但这是一个显著的区别。

不可重构的类型在某些情况下不能使用,例如在 instanceof 运算符或作为数组的元素。

所以,如果我们的代码写成这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List someList = new ArrayList<>();
boolean instanceTest = someList instanceof List<?>

代码编译后,instanceTest 为true。但是,如果我们在 List<? extends Object> 上使用 instanceof 运算符:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List anotherList = new ArrayList<>();
boolean instanceTest = anotherList instanceof List<? extends Object>;

那么第2行不编译。类似地,在下面的代码片段中,第1行编译,但第2行不编译:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<?>[] arrayOfList = new List<?>[1];
List<? extends Object>[] arrayOfAnotherList = new List<? extends Object>[1]

好了,文章到此就划上句号了,在本文中,我们主要讨论了<?> 和 <? extends Object>的异同,虽然基本上是相似的,但两者在可变与否方面存在细微差异。

DD自研的沪牌代拍业务,点击直达

【往期推荐】

索赔 100 万!只是因为一个开源插件?

2020-11-21

快速搞懂监控、链路追踪、日志三者的区别

2020-11-21

读完《Effective Java》后,总结了 50 条开发技巧

2020-11-20

35岁之后,你还会继续写代码吗?

2020-11-19

11月全国招程序员34万人,猜猜平均工资是多少?

2020-11-18

扫一扫,关注我

一起学习,一起进步

每周赠书,福利不断

深度内容

推荐加入

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿DD 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
更深入地理解Java泛型
jdk5.0中引入了Java泛型,目的是减少错误,并在类型上添加额外的抽象层。 本文将简要介绍Java中的泛型、泛型背后的目标以及如何使用泛型来提高代码的质量。
睡魔的谎言
2020/11/12
1K0
Java泛型总结
泛型是jdk5引入的类型机制,就是将类型参数化,它是早在1999年就制定的jsr14的实现。
pollyduan
2019/11/04
1K0
深入理解 Java 泛型
泛型要求在声明时指定实际数据类型,Java 编译器在编译时会对泛型代码做强类型检查,并在代码违反类型安全时发出告警。早发现,早治理,把隐患扼杀于摇篮,在编译时发现并修复错误所付出的代价远比在运行时小。
静默虚空
2022/03/23
4220
深入理解 Java 泛型
Java泛型中的细节
学习Java,必不可少的一个过程就是需要掌握泛型。泛型起源于JDK1.5,为什么我们要使用泛型呢?泛型可以使编译器知道一个对象的限定类型是什么,这样编译器就可以在一个高的程度上验证这个类型消除了强制类型转换,使得代码可读性好,而这个过程是发生在编译时期的,即在编译时期发现代码中类型转换的错误所在,及时发现,而不必等到运行时期抛出运行时期的类型转换异常。
w4ngzhen
2023/10/16
2630
java泛型详解
语法糖指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。Java中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。虚拟机并不支持这些语法,它们在编译阶段就被还原回了简单的基础语法结构,这个过程成为解语法糖。
leobhao
2022/06/28
3380
Java核心技术之什么是泛型
Java泛型(Generic)是J2SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
全栈程序员站长
2022/07/04
6770
Java核心技术之什么是泛型
那些年我们在Java泛型上躺过的枪---万恶的泛型擦除【享学Java】
泛型(Generics),从字面的意思理解就是泛化的类型,即参数化类型。 我们都知道,泛型是JDK5提供的一个非常重要的新特性,它有非常多优秀的品质:能够把很多问题从运行期提前到编译器,从而使得程序更加的健壮。
YourBatman
2019/09/03
9980
那些年我们在Java泛型上躺过的枪---万恶的泛型擦除【享学Java】
Java泛型深入理解「建议收藏」
在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数类型设置为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数,这样的方法将会更具有通用性。此外,如果将方法参数声明为接口,将会更加灵活。
全栈程序员站长
2022/09/10
8420
【小家Java】你真的了解Java泛型参数吗?细说java.lang.reflect.Type(ParameterizedType、TypeVariable、WildcardType...)
咋一看标题,你可能会说。不就是泛型吗,平时都使用着呢,没什么难的吧。 感觉了解了,但是真正的深入才知道自己了解甚少!
YourBatman
2019/09/03
3.2K0
【小家Java】你真的了解Java泛型参数吗?细说java.lang.reflect.Type(ParameterizedType、TypeVariable、WildcardType...)
年后跑路第一战,从Java泛型学起!
大家好,我是麦洛,今天来复习一下泛型。JDK 5.0 引入了 Java 泛型,允许设计者详细地描述变量和方法的类型要如何变化,使得代码具有更好的可读性。本文章是对 Java 中泛型的快速介绍,包含泛型背后的目标以及使用泛型如何提高我们代码的质量。
麦洛
2022/03/14
7120
年后跑路第一战,从Java泛型学起!
Java基础系列2:Java泛型
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架。
说故事的五公子
2019/11/10
5490
Kotlin入门潜修之类和对象篇—泛型及其原理
如果我们了解java中的泛型,那么本篇文章提到的kotlin泛型我们也不会陌生。但是如果之前没有接触过泛型或者没有真正理解泛型,本篇文章理解起来可能有些困难,不过我会尽量阐述的通俗易懂。
Android架构
2019/06/24
9430
游刃有余:玩转Java泛型
Java 中的泛型提供了一种创建可以处理不同类型数据的可重用代码的方法。它允许用户定义可操作各种数据类型的类、接口和方法,而无需牺牲类型安全性。在 Java 5 中引入的泛型已经成为 Java 编程语言的一个基本特性。
FunTester
2023/12/19
1670
游刃有余:玩转Java泛型
Java泛型探究及泛型擦除机制和如何跳过编译阶段
  也就是说,我们日常使用的泛型,JVM并不知道它的存在,因为泛型在编译阶段就已经被处理成普通的类和方法;
向着百万年薪努力的小赵
2022/12/02
5630
Java泛型探究及泛型擦除机制和如何跳过编译阶段
面试系列之-JAVA泛型剖析(JAVA基础)
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。
用户4283147
2023/08/21
4040
面试系列之-JAVA泛型剖析(JAVA基础)
Android面试题之Java 泛型和Kotlin泛型
比如没有ArrayList<int>,只有ArrayList<Integer>,当泛型擦除后,ArrayList的原始类中的类型变量T替换成了Object,但Object不能存放基本数据类型
AntDream
2024/06/13
770
Android面试题之Java 泛型和Kotlin泛型
Java泛型详解,史上最全图文详解「建议收藏」
毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课。
全栈程序员站长
2022/09/08
9390
Java 泛型详细解析
实际使用的时候就可以给这个 T 指定任何实际的类型,比如下面所示,就指定了实际类型为 LocalDate,泛型给了我们一个错觉就是通过个这个模板类 Pair<T>,我们可以在实际使用的时候动态的派生出各种实际的类型,比如这里的 Pair<LocalDate> 类。
javadaysdaysup
2024/12/01
2770
Java 泛型详细解析
JAVA回忆录之泛型篇
泛型是JDK1.5版本中加入的,在没有泛型之前,从集合中读取到的每一个对象都必须进行转化。如果有人不小心插入了类型错误的对象,在运行时的转化处理就会出错。有了泛型之后,可以告诉变一起每个集合中接受那些对象类型。编译器自动地为你的插入进行转化,并在编译时告知是否插入了类型错误的对象。
静默加载
2020/05/29
5260
Java泛型,你了解类型擦除吗?
大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个很小巧的概念,但同时也是一个很容易让人迷惑的知识点,它让人迷惑的地方在于它的许多表现有点违反直觉。
Java团长
2018/08/03
2.3K0
相关推荐
更深入地理解Java泛型
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文