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 的面试官无情三连炮,我特意推出大型刷题节目。每天一道题目,第二天给答案,前一天给小伙伴们独立思考的机会。
点下“在看”,鼓励一下?