Java 泛型三连炮,你能接得住吗?

Photo By Instagram sooyaaa

上期问题

利用 Java 中的动态绑定我们可以实现很多有意思的玩法。例如

Object obj = new String();

集合类想必是小伙伴们低头不见抬头见的类了,那么你有没有想过集合类是否可以这样玩?

List<Object> list = new ArrayList<Integer>();

不瞒你说,这是毛毛虫在一次面试中遇到的真实的面试题,当时被问的一脸懵。 如果是你,你知道怎么回答吗?

我的答案

首先我要告诉你的是,Java 集合类不允许这么玩,这样编译器会直接报错编译不通过。

我们都知道赋值一定需要是 is a 的关系,虽然 Object 和 Integer 是父子关系,但是很可惜 List<Object> 和 new ArrayList<Integer> 不是父子关系。当你这么回答时候,面试官脸上露出了狡黠的笑容,假设可以这么玩,那会有什么样的问题呢?

好吧,假设 Java 允许我们这样写

List<Object> objList = new ArrayList<Integer>();

那么接下来编译器需要来确定一下容器的具体类型,因为容器里面必须存放一种确定的对象类型,不然泛型也就没有诞生的意义了。那究竟是 Object 还是 Integer 呢?

假设一

假设它最终确定下来存放的是 Object,那就和如下代码是一样的效果了

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

这种写法是 Java 7 引入的写法,官方称之为 diamond ,就是那一对空着的尖括号,使用这种写法时候编译器会自动推算出类型为 Object。这样就相当于赋值语句 ArrayList 中的泛型 Integer 毫无意义,那么无意义的操作 Java 显然是不允许的。

假设二

我们再假设最终确定下来的是存放的是 Integer,这样问题就更大了。因为我们都知道 Object 是所有类的基类,这就代表着 objList 可以添加任何类型的对象。即我们可以做如下操作

objList.add(new String());
objList.add(new Integer());

然后我们使用容器里面的对象的时候取出来需要将它强制转换为 Integer 对象,如果容器中本来就存放的是 Integer 的对象还好,如若不是就会出现 ClassCastException。有没有发现,这样不是恰如回退到没有泛型的 Java 版本了,即 Java 1.5 之前。我们使用容器不能在编译期间保证它的类型安全了,历史回退这种傻傻的操作Java 也绝对是不允许的。

经过如上的一通分析我们发现泛型它就是不能这么玩,而且这么玩也是毫无意义。

这个时候面试官微微的点点头,表示略有赞同,你以为终于可以结束这样各种假设的骚操作了。面试官嘴角再次漏出狡黠的笑容,前面你说 List<Object> 和 ArrayList<Integer> 不是父子关系,那泛型里面有父子关系吗或者说 List<Object> 和 ArrayList<Integer> 是否存在着某种关系?

好吧。它们是兄弟,都是 List<?> 的子类。也就是说,你可以这么玩:

List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Integer>();

如上是 2 段代码是合法的,容器造出来了。那让我们来给容器里面塞一些对象进去吧。

list1.add(new Object());

不好意思你不能这么写,因为这 2 个容器里面可以存储任何东西,导致编译器都无法判断到底给里面会存储什么,它俩是 Read Only 哦。What? 那这有啥用呢?

好吧,假如有这样一个需求:写一个方法可以累加数字容器里面的元素值,那我们可以这样去写

public static long sumNumbers(List<?> list) {
    Long sum = 0l;
    for (Object num : list) {
        sum += ((Number) num).longValue();
    }
    return sum;
}

你可能注意到了,没错这里经历了一次强制类型转换,万一方法的调用方传入的是 new ArrayList<String>() 那么此处你可能会接到一个 ClassCastException 了。那有没有好的解决办法呢?有!

public static long sumNumbers(List<? extends Number> list) {/****/}

我们将方法的参数改为了 List<? extends Number> list ,这样当调用方试图传入非 Number 类型的容器实例时候在编译期就会直接报错咯,嘿嘿。

当然啦,你也可以通过 super 关键字来设置泛型参数的下限,例如 List<? super Number> list  ,那这个时候调用方法的时候就只能传入泛型参数是 Number 或者 Number 的父亲级别的容器了。

好了,这就是本题的答案啦。想必回答到此处以后,面试官心里一定心里在想:“小伙子有点东西啊”。

以上即为昨天的问题的答案,小伙伴们对这个答案是否满意呢?欢迎留言和我讨论。

又要到年末了,你是不是又悄咪咪的开始看机会啦。为了广大小伙伴能充足电量,能顺利通过 BAT 的面试官无情三连炮,我特意推出大型刷题节目。每天一道题目,第二天给答案,前一天给小伙伴们独立思考的机会。

点下“在看”,鼓励一下?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值