Java集合中使用泛型参数及泛型上下限的问题

今天在写一个多线程调度器,先花了十分钟把逻辑理清,需求如下:

1.我们有一张数据库表(单表),存放着不同来源的订单(所有来源的订单在存入这张表之前都会转化为这张表规定的字段格式),假设一共十个不同的渠道来源。

2.然后我们需要定时轮询这张表,对所有订单向对应渠道发起校验,以及监测订单是否是否过期。

3.轮询的规则是这样的,根据指定渠道查询这张表中该渠道的所有订单,然后进行相应处理


我的解决思路是:

首先,给每个渠道定义一个对应的任务,这个任务要完成的就是轮询数据库中所有该渠道的订单并进行处理,使用Map作为渠道的任务池。有了任务,还得让它跑起来呀,而且还不能让它无限制的运行,所以考虑用Future<Boolean>来给每一个任务定时,所以把前面的梳理一下,我们要把每个渠道的任务,以及每个任务的一次执行对应起来(读者可以先想想你们会用到什么数据结构来把这三者存储起来)。

但我们总共有十个任务呢,如果让它们各自运行那岂不是像一盘散沙吗,所以我们还需要一个任务调度器,这个任务调度器负责把不同的渠道对应的任务添加进任务池,然后轮询任务池,并执行任务池中的任务,同时还要把每个任务的每次执行存储起来,方便我们对它们进行监测,防止有任务无限执行下去。

最后,我们的十个任务也不需要时刻运行,每十分钟触发一次。所以我们还需要一个任务触发器,每十分钟启动一次任务。


好了,思路终于理清了,再来进行具体的设计

1.先设计数据结构,用一个嵌套的Map来把渠道---任务---执行结果对应起来

Map<String, Map<? extends Callable<Boolean>,Future<Boolean>>>

2.给每一个渠道定义一个Task,实现Callable<Boolean>接口,完成向对应渠道发起校验以及过期检测的任务;

3.写一个TaskDispatcher类(正确的设计方式其实是定义一个接口,里面添加流程中涉及到的方法声明,然后让这个类实现这个接口),在里面实现添加如下几个方法:

addChannel(String channel){}//这个方法用来向任务池中添加渠道对应的任务

executeTask(String channel){}//执行对应渠道的任务,并获取任务执行的Future<Boolean>存储起来

pollTasks(){} //轮询任务池中的所有任务,并执行它们

4.写一个任务触发器,每十分钟启动一次所有任务。

ps:写到这里我突然想到,如果我们不需要关心任务执行的结果,只需要知道它有没有在规定的时间执行完,我们完全可以把把任务结果独立拉出来放在一个List中,然后另起一个任务轮询List中的所有任务结果,如果发现没有再规定时间内完成的则结束它们。这样我们就免去了把渠道---任务---执行结果对应起来的大麻烦。


接下来撸代码的时候遇到这样一个问题,我首先定义了任务池

private Map<String, Map<? extends Callable<Boolean>,Future>> map= new WeakHashMap<String,Map<? extends Callable<Boolean>,Future<Boolean>>>();

然后添加任务的时候出错了

Map<? extends Callable<Boolean>,Future> tfMap=new HashMap<? extends Callable<Boolean>,Future>();   

tfMap.put(new Task(), null); 

错误提示如下:

The method add(capture#3-of ? extends A) in the type List<capture#3-of ? extends A> is not applicable for the arguments (A)

我百思不得其姐大哭,问题究竟出在哪里呢?

我又试着把new HashMap<? extends Callable<Boolean>,Future>();  换成new HashMap<>(); 

结果还是报错,于是我开始翻阅各种资料博客,试尽了各种办法,始终报错。就在我准备放弃的时候无意间在一篇博客中瞥见这样一句话:

集合中使用泛型限定:? extends Super(或? super Child),这种集合是只读的,因为“表达式右边永远是一个确定的值”

上面这句话很耐人寻味,我细细推敲了一下恍然大悟

从源头上来思考,泛型的作用就是限定某一种容器中只能装某一个特定的类对象,而泛型通配只是限定了范围,实际内存中还是要求某一容器必须装同种类对象,这就是所谓的“表达式右边永远是一个确定的值”,因为表达式右边代表内存中的一块内容,左边是给方便给程序员看的,是对这块内存中的内容起的一个别名,换句话说,程序在运行的时候必须能确定我要分配多大的内存。

具体到这个例子上来分析就是,map只是我们抽象出的一个容器,提醒我们这个容器里面可以放哪些类型(或范围内)的东西,由于Java的多态,我们能从里面取出我们想要的对象(因为可以用父类表示子类对象),这就是所谓的“只读性”;可是如果要往里存对象的时候,我们就需要明确告诉JVM我要存哪一种具体的类对象,这样JVM才可以分配出指定大小的内存来存储对象。

理解了上面的内容,我们就可以作出如下修改:

Map<Task,Future> tfMap=new HashMap<Task,Future>();   //分配一个装载特定类对象的容器

tfMap.put(new Task(), null);    //现在添加就不会出错


最后我又尝试了写一个最简单的例子来验证我的理解,事实证明的确如此:

class A{}
class B extends A{}

List<? extends A> list=new ArrayList<A>();
list.add(new A());  //报错
list.add(new B());  //报错
list=Arrays.asList(new A(),new B());  //正确
List<? extends A> list2=new ArrayList<A>();
list2.add(new A()); //报错
list2.add(new B()); //报错
list=Arrays.asList(new A(),new B()); //正确

Arrays.asList之所以能能成功是因为,内部有一个new ArrayList<A>()操作,申请了一个装载特定类对象的容器
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值