美文网首页JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统
王垠的这道Java高级面试题,看似容易,其实很难

王垠的这道Java高级面试题,看似容易,其实很难

作者: adminmane | 来源:发表于2020-06-13 15:27 被阅读0次

    最近,在垠神的微博上,看到他出的一道关于Java的高级面试题,与各位同学分享一下,题目内容如下图:

    王垠的这道Java高级面试题,看似容易,其实很难

    这道题目主要是关于Java中协变与逆变的思考,先说一下运行结果,编译期会放行,运行期会抛异常:

    java.lang.ArrayStoreException: java.lang.Integer
    

    正如《Effective Java》的作者所说,Java允许数组协变是Java本身的缺陷,他也没闹明白为什么要这么设计,估计是为了妥协吧。

    关于Java协变与逆变的思考,我也在网上找了一些资料和自己的理解,说实话,写这篇文章确实有难度,不能保证文章的内容完全正确,欢迎各位同学指正。

    # 什么是协变与逆变?

    其实关于它的定义,维基百科上面讲的很详细,我就在这里简单的说一下。

    逆变与协变用来描述类型转换(type transformation)后的继承关系。

    其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)

    f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;

    f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;

    f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

    看这些概念是不是有点晦涩难懂,其实在《Thinking in Java》讲泛型那章提到一个例子挺好的说明问题的。

    class Fruit {}class Apple extends Fruit {}class Orange extends Fruit {}
    

    比如,有两个类型Apple和Fruit,它们之间的关系是Apple≤Fruit,即Apple是Fruit的子类型;通过它们构造出对应的数组类型Fruit[]和Apple[],最终这对数组类型的关系是Apple[] ≤ Fruit[],即数组类型也保持着原类型之间的关系,这说明数组具有协变性。

    Fruit[] fruits = new Apple[10];
    

    从上面的代码,我们也可以看出:子类型的数组可以赋予父类型的数组进行使用。

    接着,我们再来看看下面这句代码:

    fruits[0] = new Orange();//抛 java.lang.ArrayStoreException
    

    你注意到没有,Java为了确保类型安全,必须明确知道数组内部元素的类型(声明时),而且记住这个数组类型,每次往数组里面插入新元素(运行)时,都会进行类型检测,如果不匹配,就会抛出异常:
    java.lang.ArrayStoreException。

    到这一步,我们便能理解了垠神上面的那个代码为啥会抛异常。

    # 数组为什么要设计协变?

    我挺赞同文章开头那种说法,在Java SE5之前没有泛型,急需要这种设计来解决当务之急,所以才这么设计。

    就比如我们常用的Arrays.equals()方法,代码如下:

    王垠的这道Java高级面试题,看似容易,其实很难

    由于没有泛型,所以把数组这么设计,来进行妥协,让多态的作用发挥出来,所以,是不是缺陷?就需要根据具体的时间点和环境而议。

    # 谈谈泛型

    Java的泛型设计,确实很牛逼,可以穿梭于协变、逆变与不变之间。

    ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch
    

    上面这句代码编译时,会报错:type mismatch。由于Integer ≤ Number,而实例化的list对象是错误的,因此,默认情况下,Java的泛型是不变的。

    有同学可能会问:有时需要实现逆变与协变,怎么办呢?

    这时,可以使用通配符 ? 。

    <? extends>实现了泛型的协变,比如:

    List<? extends Number> list = new ArrayList<Integer>();
    

    <? super>实现了泛型的逆变,比如:

    List<? super Number> list = new ArrayList<Object>();
    

    那问题又来了,究竟什么时候用extends什么时候用super呢?可以参考《Effective Java》这本书,见下图:

    王垠的这道Java高级面试题,看似容易,其实很难

    总结一下吧:

    • 要从泛型类取数据时,用extends;
    • 要往泛型类写数据时,用super;
    • 既要取又要写,就不用通配符(即extends与super都不用)。

    上面这些总结很浅显,只是对Java基础知识的回顾,更深层的研究,还需要各位同学进一步深入研究。

    最后,垠神希望得到更深层次的理解,我达不到那个水平

    王垠的这道Java高级面试题,看似容易,其实很难

    ,希望你能把这篇面试题转发出去,看看你身边有没有垠神期待的人才?

    最后小编整理了一套技术资料不仅能精准消除技术盲点、累计面试经验,更可以攻克JVM、Spring、分布式、微服务等技术难题。

    王垠的这道Java高级面试题,看似容易,其实很难

    海量电子书,珍藏版

    王垠的这道Java高级面试题,看似容易,其实很难 王垠的这道Java高级面试题,看似容易,其实很难 王垠的这道Java高级面试题,看似容易,其实很难 王垠的这道Java高级面试题,看似容易,其实很难

    1、加微信获取


    1892324-20200408173704995-149739833.png

    相关文章

      网友评论

        本文标题:王垠的这道Java高级面试题,看似容易,其实很难

        本文链接:https://www.haomeiwen.com/subject/cezatktx.html