当前位置: 首页 >文章 > 再有人问你什么是范型,就把这篇五千字文章甩给他!​
收藏
分享

再有人问你什么是范型,就把这篇五千字文章甩给他!​

举报小虎转载君小虎转载君发布于 2020-06-03560阅读0点赞
希望这一系列泛型主题相关的内容能让你更加深入、透彻的理解泛型、看到泛型、运用泛型。...

首先,对Java8 以及 泛型 还不熟悉的同学建议是先去学习,再来阅读这篇文章可能对你收获更大哦,我们今天聊的是Java8新特性—泛型类型推断,可能有些同学会说这还不简单,泛型有啥可以讲得,在开发中一顿<>操作猛如虎,如果不行,那就两套<<>>

同学,我们先来康康这个例子,定义一个简单的泛型类 Some。

这个类很简单,他的值由一个 supplier 函数通过静态工厂方法提供,还有额外两个方法 peek 、 get ;注意 peek 返回的是 this ,这样方便链式调用。


上面这段代码一如预期的正常编译,并且打印结果 [a, b, c],下面我们做点小改动,链式调用 peek.将 System.out.println 作为 consumer 传入 peek 。

上面的代码将会产生出乎预料的结果,无法正常编译。

编译器报类型不兼容的原因,是因为Java泛型是 invariant (可以参见covariant VS invariant)。Java语言规范 4.10.Subtyping 章节有这么句清晰的描述:

子类型关系不会扩展到参数化类型,也即是如果 T 是 S 的子类型,不能推断出 C<T> 是 C<S> 的子类型。

那为什么上面第一个列子调用方式没有 peek 可以,peek 返回 this ,和 of返回结果一样,第二个例子调用却不可以。

这里涉及到Java8引入的泛型目标类型推断(JEP 101: Generalized Target-Type Inference),泛型推断让编译器能够利用上下文信息来推断出合理的类型。下面逐步分析。


笔者所用的 IDE 是 IntelliJ IDEA , 这里有个小技巧, Mac 下按住 Command ( Window 下应该是 Ctrl , 待验证) 键同时将光标移动到泛型方法上,既可以看出Java编译器根据上下文推断出的类型,如果想让浮动弹窗长期保持,按住 Command 的同时将光标移动到浮窗,点击下,这时即可松开 Command , 移走光标浮窗也会保持,若未点击浮窗,则松开 Command 键,浮窗就会消失。

从截图中可以看出编译器推断 类型变量 T 是 List<String>,peek 返回结果是 Some<List<String>>类型。和表达式左边的类型是不一样的。


这也就清楚为什么第二个例子会报类型不兼容了。因为 List<? extends CharSequence>与List<String> 是不兼容的,原因就是上面提到的 Java泛型是 是 invariant 。


再看看下面的例子,注意 of 是一个静态工厂方法。

第一个 of 推断出 T 是 List<String>,返回类型是 <Some<List<String> ,第二个of T是List<? extends CharSequence>,返回类型是List<? extends Object>
为什么编译器会推断出 T 同时是两个类型?

#注意方法声明 of 是一个静态泛型方法,实则前后两个 of 方法的类型变量 T 根本不是同一个,所以可以类型不同,最后赋值给表达式左边的是第二个 of 的返回值,而他们类型是一样的,所以可以正常编译。

实际第一个 of 中 T 无论是 List还是 List, 表达式都合法,因为最终返回类型由链路中最后一个方法决定,只要它的类型和表达式左边被赋值对象类型兼容就合法。

由于 peek 是实例方法,所以他的变量类型 T 其实早已由前面 of 构造出的对象决定了。那为什么 Some.of(() -> Arrays.asList("a", "b", "c")) 在两个表达式中会推断出不同类型?

这是因为 Some.of(() -> Arrays.asList("a", "b", "c")) 在两个语句中所处的上下文不一样,我们知道泛型目标类型推断,编译器需要结合上下文来推断更合理的具体类型或者是它的子类。

Some.of(() -> Arrays.asList("a", "b", "c")) 这个语句中,编译器可以推断出类型变量 T 可以为 List、 List 、 List , 但有多个候选类型时,编译器会选择更为具体、继承链中更接近的类型。下面 JLS 关于类型推断的一句描述可以佐证。

Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.

第一个截图中, of 所处上下文,编译器既要让 of 的结果类型满足表达式左边的类型(针对类型变量T) List<? extends CharSequence>又要满足入参的类型(List<String>、 List<? extends CharSequence>、 List<? extends Object>任一皆可),这时会取两者交集,所以最终推断出 T 是 List<? extends CharSequence>。

第二个截图中, of 所处上下文并不是表达式链路的最后,他的返回结果不需要赋值给表达式左边的变量,所以编译器类型推断时只需要考虑兼容入参类型,这时会选择三种候选类型中最具体的List<String>。而后面的 peek 方法实则是 of 构造方法返回对象的实例方法,它的类型已经确定是 List<String>,故不存在类型推断的过程。peek 的返回类型也就知道是 Some<List<String>>。

有两种方式可以解决图2调用 peek 的问题:

现在我们知道图2中的 of 是由于没有提供足够的上下文供编译器参考,所以可以通过显示指定具体泛型类型。


图2是由于 peek 的链式调用,导致前面的 of 失去了返回类型的上下文约束信息,所以可以不采用链式调用,给予编译器更多的上下文信息。


下面看道题,根据上面的分析,可以类推下面的使用方式是错误的,因为 new ArrayList<>() 所处的上下文没有提供任何类型约束,编译器只能推断 ArrayList 的类型变量是 Object , Iterator 自然无法和 Iterator兼容。
1Iterator<String> it = new ArrayList<>().iterator(); //

那有没有办法写出一个类型安全的赋值方式?

1这里 iter 方法所处的上下文,为了满足表达式左边的类型,编译器推断出 T 的类型是 String ,而 new ArrayList<>() 也是泛型,假设他的类型变量是 E ,为了满足 iter 入参的类型, 编译器推断 E 也是 String , 上面的错误实例中之所以 new ArrayList<>() 被推断为 Object , 是因为语句既没有作为返回类型受到赋值类型的约束,也没有作为入参受到方法声明的约束,缺失这些上下文约束提示,编译器只能推断出 Object 。

2根据前面的理论知识,你应该不难推测出,下面的写法仅仅是比上面少了结果赋值,那么这种情况由于 iter 缺失返回类型约束,入参本身也是泛型,也不能提供上下文约束条件,所以 iter 的推断结果是 Object , new ArrayList<>() 由于 iter 是 Object ,所以 它的类型变量推断结果也是 Object .

1iter( new ArrayList<>() );

下面通过 idea 的自动提取变量来验证我们的猜测。通过选中 1处语句,按下 idea 提取变量快捷键,就会补全成 2这样的语句,这个其实就是通过编译器的泛型目标类型推断来推断变量类型。

最后再看看泛型推断在泛型实例化中的作用

没有泛型推断前,我们必须按照@1来实例化泛型类,这种使用方式看起来很累赘,前后都要声明参数化类型。有了泛型推断,就可以用如@2简洁的写法,用一对空尖括号 <> 代替。但是如果你用如@3这种写法,将会得到一个编译器警告,因为 new HashMap() 表示一个 raw type HashMap (不需要泛型推断),无法确定元素具体类型,因此将HashMap类型赋值给Map<String, List<String>>

1Map<String, List<String>> myMap = new HashMap<String, List<String>>(); //@1

2Map<String, List<String>> myMap = new HashMap<>(); //@2

3Map<String, List<String>> myMap = new HashMap(); //@3  unchecked conversion warning

希望这一系列泛型主题相关的内容能让你更加深入、透彻的理解泛型、看到泛型、运用泛型。


本文原创,未经作者允许不可转载!
更多内容,欢迎关注作者微信公众号:橘松Java技术窝!


0条评论
别默默看啦~登录/注册一起参与讨论吧~

暂无评论

请选择举报理由

违反法律法规

侵犯个人权益

有害网站环境

更多训练营>>

为你推荐 · 训练营(全勤打卡报名费全额返累计全额返用户133,673人)

电商海报设计训练营
距离开班仅剩6天74人已报名
【5月】零基础手绘插画训练营
距离开班仅剩6天69人已报名
【5月】零基础动态表情包创作训练营
距离开班仅剩23天16人已报名
猜你喜欢
特惠
充值
7折购
今日还在继续学习的你,太棒了!
7
折扣券可用于
年费无限VIP
立 即
使 用
此活动优惠不可与其他活动叠加使用
有效期:000000
消息
登录即可查看消息记录
建议
意见
官方
客服
在线咨询客服热线

您可以与在线客服进行沟通获得帮助

工作日:9:00~22:00节假日:9:00~18:00

联系在线客服

您可以电话联系客服进行沟通获得帮助

工作日:9:30~18:30

400-862-9191
虎课
积分
免费学习89000+个教程!
配套素材、源文件一键下载!
昨日学员已学习了29,770
并提交了155份作业!
登录后立即学习!
loading
微信扫码关注即可登录
您需要同意协议才可以进行登录
登录虎课网,每天免费学课程全站 89000+ 视频会员教程 | 每日可免费学 1
为确保账户信息安全
请先进行真实姓名验证后进行充值付款
立即验证