java泛型改进_java泛型高级篇 - 真正理解协变与逆变 | 鱼儿的博客

无论你是否听过java泛型的协变与逆变,我们直接进入例子,一起来看一下java泛型比较高级的用法。

例子1:copy函数

第1个例子我们实现copy函数,它将List中的元素复制到List中。

Java

List src = Arrays.asList(1,2,3,4,5);

List dst = new ArrayList();

1

2

Listsrc=Arrays.asList(1,2,3,4,5);

Listdst=newArrayList();

不用泛型

copy1实现的参数类型是死的:

Java

private static void copy1(List src, List dst) {

for (Integer obj : src) {

dst.add(obj);

}

}

1

2

3

4

5

privatestaticvoidcopy1(Listsrc,Listdst){

for(Integerobj:src){

dst.add(obj);

}

}

然后这样调用:

Java

copy1(src, dst); // 死板,不通用

1

copy1(src,dst);// 死板,不通用

因为Integer是Number的子类,所以自然可以存储到List中。

问题:copy1函数只能搞定List拷贝给List,无法处理其他类型。

基本泛型

copy2由简单实现改进,引入泛型参数即可:

Java

private static void copy2(List src, List dst) {

for (T1 obj : src) {

dst.add((T2)obj);

}

}

1

2

3

4

5

privatestaticvoidcopy2(Listsrc,Listdst){

for(T1obj:src){

dst.add((T2)obj);

}

}

然后这样调用:

Java

copy2(src, dst); // 能用,但没有编译期约束,运行不安全(比如dst是List类型)

1

copy2(src,dst);// 能用,但没有编译期约束,运行不安全(比如dst是List类型)

我们知道T1(Integer)继承自T2(Number)的子类,所以加入dst时只需要强制向上转型即可通过编译。

问题:T1继承自T2是我们假设的,如果dst是一个List,那么上述代码将试图将Integer obj转型为String,虽然代码通过了编译但是在运行时将失败。

如果可以约束T1与T2之间的父子关系,那么这个函数将更加健壮。

泛型加强约束

copy3在泛型基础上引入约束:

Java

private static void copy3(List extends T> src, List super T> dst) {

for (T obj : src) {

dst.add(obj);

}

}

1

2

3

4

5

privatestaticvoidcopy3(List<?extendsT>src,List<?superT>dst){

for(Tobj:src){

dst.add(obj);

}

}

调用:

Java

copy3(src, dst); // 最灵活,编译期约束,运行一定安全

1

copy3(src,dst);// 最灵活,编译期约束,运行一定安全

我们期望的约束是:src中的元素类型A必须继承自dst中的元素类型B(A与B一样也可以),那么就可以安全的将src中的元素加入到dst中(A向上转型为B)。

既然A类型必须继承自B,那么A与B继承层次中间一定存在某个类型T,所以我们可以做出约束:

src里必须是T的子类:List extends T>,上述例子中 extends T>就是Integer。

dst里必须是T的父类:List super T>,上述例子中 super T>就是Number。

extends T>作为T的子类,当然可以向上转型为 super T>作为T的父类,所以这个约束就是我们想要的效果。

我们可以猜想,编译器也一定会推断T为Number类型,此时可以令上述约束成立,实际上我们并不需要关心T到底是啥,反正我们对T类型也做不了什么事。

此时,我们的copy3函数功能已经很强大,可以支持将List拷贝给List这样的调用,同时也会在编译器就禁止掉将List拷贝给List这样的错误写法。

例子2:map函数

mapper是一个泛型接口:

Java

private static interface Mapper {

R map(T t);

}

1

2

3

privatestaticinterfaceMapper{

Rmap(Tt);

}

接受T类型参数,返回R类型参数。

我们接下来实现map函数,接受1个List与1个Mapper,返回1个新List。

我们固定输入是List类型,返回为List类型。

Java

List list = Arrays.asList(1,2,3,4,5);

1

Listlist=Arrays.asList(1,2,3,4,5);

不用泛型

实现map1函数:

Java

private static List map1(List list, Mapper mapper) {

List l = new ArrayList();

for (Integer t : list) {

Number r = mapper.map(t);

l.add(r);

}

return l;

}

1

2

3

4

5

6

7

8

privatestaticListmap1(Listlist,Mappermapper){

Listl=newArrayList();

for(Integert:list){

Numberr=mapper.map(t);

l.add(r);

}

returnl;

}

调用:

Java

// 写死类型

List ret1 = map1(list, new Mapper() {

@Override

public Number map(Integer integer) {

return integer * 1.2;

}

});

1

2

3

4

5

6

7

// 写死类型

Listret1=map1(list,newMapper(){

@Override

publicNumbermap(Integerinteger){

returninteger*1.2;

}

});

map1函数写死了List元素类型、Mapper输入类型、Mapper返回值类型。

问题:不能支持其他类型,需要引入泛型。

基本泛型

map2将上述Integer换成T泛型参数,Number换成R泛型参数:

Java

private static List map2(List list, Mapper mapper) {

List l = new ArrayList();

for (T t : list) {

R r = mapper.map(t);

l.add(r);

}

return l;

}

1

2

3

4

5

6

7

8

privatestaticListmap2(Listlist,Mappermapper){

Listl=newArrayList();

for(Tt:list){

Rr=mapper.map(t);

l.add(r);

}

returnl;

}

调用:

Java

// 能用,但mapper入参类型必须与list泛型完全一样,返回值类型必须与ret2泛型完全一样

List ret2 = map2(list, new Mapper() {

@Override

public Number map(Integer integer) {

return integer * 1.2;

}

});

1

2

3

4

5

6

7

// 能用,但mapper入参类型必须与list泛型完全一样,返回值类型必须与ret2泛型完全一样

Listret2=map2(list,newMapper(){

@Override

publicNumbermap(Integerinteger){

returninteger*1.2;

}

});

原理很简单,输入T类型的列表,经过mapper处理T类型后返回R类型的列表。

问题:List ret2要求R类型匹配为Number,list要求T类型匹配为Integer,因此就导致Mapper也必须是Mapper,使用起来看不出啥问题,但灵活性是有一定局限的,下面解释。

泛型放宽约束

当map函数遍历List中的每个Integer时,如果Mapper接受Integer当然最准确,但如果Mapper能够接受Number类型作为输入,那么其实把Integer传给Mapper也是可以运行的,因为Integer可以向上转型到Number。

同样道理,让Mapper返回Number类型当然最准确,但如果Mapper返回是Number子类(比如Double),其实Double是可以向上转型插入到List中的,这样也完全正确。

所以,我们应该让Mapper的泛型参数具备灵活性,而不是完全等于T和R。

理解了就非常容易写出map3函数:

Java

private static List map3(List list, Mapper super T,? extends R> mapper) {

List l = new ArrayList();

for (T t : list) {

R r = mapper.map(t);

l.add(r);

}

return l;

}

1

2

3

4

5

6

7

8

privatestaticListmap3(Listlist,Mapper<?superT ,?extendsR>mapper){

Listl=newArrayList();

for(Tt:list){

Rr=mapper.map(t);

l.add(r);

}

returnl;

}

调用:

Java

// 最灵活,mapper入参类型只要是list泛型的父类即可,返回值类型只要是ret3泛型的子类即可

List ret3 = map3(list, new Mapper() {

@Override

public Double map(Number number) {

return number.doubleValue() * 1.2;

}

});

1

2

3

4

5

6

7

// 最灵活,mapper入参类型只要是list泛型的父类即可,返回值类型只要是ret3泛型的子类即可

Listret3=map3(list,newMapper(){

@Override

publicDoublemap(Numbernumber){

returnnumber.doubleValue()*1.2;

}

});

读懂上述泛型必须学会阅读顺序!!

根据实参和返回值,T和R类型先被明确:

List ret3明确了返回值的R就是Number。

List list明确了T就是Integer。

在T和R已明确情况下,我们通过? super T的方式令Mapper的入参类型放宽,只要是T(Integer)的任意父类即可接受T的传入。通过? extends R的方式令Mapper的返回类型放宽,只要是R(Number)的任意子类即可插入到List l中。

总结

你应该听说过java的协变、逆变、PECS原则等概念,我认为理解上述例子应该是不需要死记硬背的:

1,理解这3个符号之间的继承父子关系: extends T> 继承自 T 继承自 super T>

2,结合向上类型转换,一切皆可分析。

如果文章帮助了你,请帮我点击1次谷歌广告,或者微信赞助1元钱,感谢!

c68972f84f7c4f47f59a1f69f0608e10.png

知识星球有更多干货内容,对我认可欢迎加入:

6c9a48ad74e3675cedd6ca98c1fd0a1f.png

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

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

相关文章

java实训遇到问题解决_「instantiationexception」关于java出现 java.lang.InstantiationException异常的分析与解决方案 - seo实验室...

instantiationexceptionjava.lang.instantiationexception 是指不能实例化某个对象&#xff0c;一般在我们使用java反射机制去创建某个对象的时候实例化到了一个抽象类或者接口(java中抽象类和接口是不能被实例化)&#xff0c;而今天我遇到的则是我在使用反射机制实例化某个持久…

java对外发布接口文档_java之接口文档规范

一、xxxxxx获取指定任务爬取的所有url的接口接口名称&#xff1a;xxxxxx获取指定任务爬取的所有url的接口访问链接&#xff1a;http://IP:PORT/crwalTask/findUrlExceptionById?ctIdctIdVal&timetimeVal&limitlimitVal传入参数类型&#xff1a;String,int参数内容:返回…

java画好看坦克_坦克大战第一节——画出自己的坦克(新手篇)

刚刚开始学习java&#xff0c;对java不是很熟悉&#xff0c;但是自己的兴趣挺喜欢java。现在自己在自学java做一个小游戏&#xff0c;坦克大战。自己现在完成了画出自己的坦克和坦克的移动方向。希望各位大神指导一下我这个刚刚学java的新手小白。我会每完成一个阶段&#xff0…

java中timer类包_Java~util包中Timer的使用, 演示cancel方法 和 对比schedule和scheduleAtFixedRate方法...

TimerTimer类的主要作用就是设置计划任务&#xff0c;但封装任务的类却是TimerTask类执行计划任务的代码要放人TimerTask的子类中&#xff0c;因为TimerTask是一个抽象类。而且要重写其run方法 因为这是一个抽象方法.常见问题任务执行完了, 但进程并没有销毁,通过源码查看构造方…

centos eclipse java_CentOS7 安装 Eclipse

咳咳&#xff0c; 虽然大神的 Linux 都是命令行的&#xff0c; 但是谁让 LZ 是 LJ&#xff0c; 桌面开俩终端不觉得更好么。。。CentOS7 安装 Eclipse&#xff0c; 比在 Window 系统下安装 Eclipse 要复杂得多。。参考 &#xff1a; https://segmentfault.com/a/11900000026517…

java opencv orb_opencv python ORB算法

理论ORB(Oriented FAST and Rotated BRIEF)是一种快速特征点提取和描述的算法,这个算法是由Ethan Rublee, Vincent Rabaud, Kurt Konolige以及Gary R.Bradski在2011年一篇名为“ORB&#xff1a;An Efficient Alternative to SIFTor SURF”的文章中提出.ORB算法分为两部分&#…

php中define的参数_php中define的用法有哪些

php中define的用法&#xff1a;1、【define()】函数定义一个常量&#xff1b;2、定义常量名称及值&#xff0c;代码为【define(name,value,case_insensitive)】&#xff1b;3、定义一个大小写敏感的常量。php中define的用法&#xff1a;1、define()函数理解1define()函数定义一…

php 登陆微博,PHP调用微博接口实现微博登录的方法示例

在平时项目开发过程中&#xff0c;除了注册本网站账号进行登录之外&#xff0c;还可以调用第三方接口进行登录网站。这里以微博登录为例。微博登录包括身份认证、用户关系以及内容传播。允许用户使用微博帐号登录访问第三方网站&#xff0c;分享内容&#xff0c;同步信息。1、首…

2017php行情,2017年蔬菜行情特点及未来蔬菜价格走势分析

2017年蔬菜行情整体特点一、菜价走势前期低迷&#xff0c;后期逐渐回升&#xff1a;1.2017年1&#xff5e;10月的蔬菜平均价处于近3年同期的最低点&#xff1b;2.1&#xff5e;5月菜价创近5年同期最低。南方产区的实心菜花、散菜花、西兰花、团生菜、白萝卜、大白菜&#xff0c…

java实现layui静态表格分页,layui——数据表格分页实例

layui实现数据表格table分页功能&#xff0c;异步加载&#xff0c;表格渲染。总体流程&#xff1a;layui的数据表格设置分页参数开启可以从请求中看到如下图&#xff0c;所以我们需要在后端控制器接收分页数据page和limit进行操作&#xff0c;下面直接上代码分页链接框架&#…

mysql多列 groupby,MySQL多表查询之GroupBy

需求&#xff1a;根据主键id查询到该顾客最近的一次消费记录SQL代码如下&#xff1a;SELECTcbi.id,cbi.mob,cbi.identity_card,bcil.remark,bcil.orders_no,bcil.brand_no,bcil.with_date,bcil.scoreFROMcustomer_base_info cbiLEFT JOIN(SELECTA.customer_id,A.with_date,A.re…

java j2ssh替代jsch,jsch设置ssh协商算法优先级

最近接触jsch这个包&#xff0c;发现在默认情况下&#xff0c;jsch登录ssh的时候&#xff0c;协商的加密算法和mac算法都不是最高优先级的&#xff0c;这个时候需要手动配置一下算法列表&#xff0c;将强度高的调整在算法列表的前面&#xff0c;这样ssh链接的时候&#xff0c;如…

java 截取汉字首字母,java 取汉字首字母

有时候&#xff0c;可能会有一些类似这样的需求&#xff1a;对于这样的效果&#xff0c;我们可以有类似这样的解决方案&#xff1a;package bys.utils;import java.io.UnsupportedEncodingException;/*** Created by toutou on 2014/2/21*/public class ChineseCharacterHelper…

linux操作系统网络,网络安装linux操作系统

网络安装安装的准备首先配仓库然后需要安装的服务Yun y install tftp-serverYum y install dhcp开始实验配仓库安装tfpt包并进行配置Tftp-server dhcp tftp后&#xff0c;会在/var/lib/tftpboot这个文件&#xff0c;这个文件是作为linux安装的引导文件&#xff0c;将/mnt/isoli…

Linux使用ftp传输10g的文件,Ubuntu 16.04 安装ftp服务器传输文件

最近在搞深度学习&#xff0c;老师比较宝贝他的服务器&#xff0c;要求我以后负责管理服务器。往后所有要使用服务器的人都必须向我申请账号&#xff0c;然后只允许客户端访问&#xff0c;使用文件传输软件传输文件。像我这样一个Linux菜逼&#xff0c;这种要求不是赶鸭子上架嘛…

linux创建文件内容三行,shell之创建文件及内容的方法示例

shell之创建文件夹&#xff1a;[rootvbox-nginx shell_command]# vi ./mkdir.sh#!/bin/shparentDir"/media/sf_Project/self/smarty-frame/application/$1"fileName$2dirAndName$parentDir/$fileNameif [ ! -d "$dirAndName" ];thenmkdir $dirAndNameecho …

linux设备模型的主要功能,Linux设备模型(3)

Linux设备模型(3)_Uevent作者&#xff1a;蜗蜗 发布于&#xff1a;2014-3-10 20:39分类&#xff1a;统一设备模型1. Uevent的功能Uevent是Kobject的一部分&#xff0c;用于在Kobject状态发生改变时&#xff0c;例如增加、移除等&#xff0c;通知用户空间程序。用户空间程序收到…

lua_path环境变量设置linux,ubuntu16.04安装lua环境

1.官网下载源码 https://www.lua.org/download.htmlcurl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gztar zxf lua-5.3.5.tar.gzcd lua-5.3.5make linux test2.安装中报错lua.c:82:31: fatal error: readline/readline.h: 没有那个文件或目录compilation terminated.: recip…

皮尔洛和c罗讲什么语言,皮尔洛:如果我跟C罗是队友 我可能能成历史助攻王

皮尔洛&#xff1a;欧冠夺冠热门不包含皇马腾讯体育11月2日讯 近日皮尔洛在接受媒体采访时表示&#xff0c;本赛季欧冠的争夺将会十分激烈&#xff0c;除了尤文外&#xff0c;有四支球队也是欧冠桂冠有利的争夺者&#xff0c;但并不包括卫冕冠军皇马。另外皮尔洛还表示&#xf…

android 调用系统短信接口开发,聚合数据Android SDK 短信api接口验证演示示例

1.到libs聚合SDK是聚合数据平台,为移动开发者提供的免费数据接口.使用前请先到聚合平台(http://www.juhe.cn/)注册,申请相关数据.2.下载聚合数据SDK,将开发包里的smscaptcha_sdk_v_1_1.jar拷贝到libs根目录下&#xff0c;将libJuheSDK_v_1_0.so拷贝到libs\armeabi目录下,如图:3…