首页 > 面试中泛型问题一文搞定
头像
大黄奔跑
编辑于 2020-12-28 21:37
+ 关注

面试中泛型问题一文搞定

关注我,可了解更多有趣的面试相关问题。

写在之前

本文主要从面试角度讲述泛型问题在面试中该如何回答,如果只想看问题答案,可以重点留意灰色背景的问题,其他的文字主要是扩展内容。

关于泛型,Jdk1.5以后推出来的特性,一经推出就收到Java程序员大力赞扬,因为真的解决一些编码问题。下面我们来看看如此重要的泛型,在面试中会怎么考察呢。

面试问题概览

下面我罗列一些大厂面试中,对于泛型一些常见问题。

  1. 泛型了解吗?介绍一下泛型?【快手】
  2. 泛型实现的原理,为什么要实现泛型【阿里】
  3. 泛型实现原理 【去哪儿】
  4. java的泛型,superextend的区别?泛型擦除的原理【华为】
  5. 什么是泛型,什么时候需要利用泛型【字节跳动】
  6. java的泛型,有什么缺点【腾讯】

可以看到泛型在各个大厂面试中已经成为了笔考题目,回想自己当初校招,因为泛型回答不好而错失阿里Offer,至今仍然悔恨不已。

真实面试回顾

一个身着灰色格子衬衫,拿着闪着硕大的🍎logo小哥迎面走来,我心想,logo还自带发光的,这尼玛肯定是p7大佬了,但是刚开始咱们还是得淡定不是。

大黄同学是吧,我看你简历上面写熟悉Java基础以及常见的Java特性。那你先说说你对泛型的理解吧

此刻咱们得淡定,虽然自己准备过,也得慢慢道来。

面试官您好,泛型是JDK1.5之后提出的新概念,本意是让同一套代码可以适应更多的类型。

大家都写过议论文对吧,对于一个问题需要正反两面说,优点、缺点,一一道来。

缺点:在没有泛型之前一旦某个接口定义了参数为某个类型,则实现了该接口的方法必须采用同样类型参数,不利于程序的扩展。
优点:

  1. 比如在创建容器的时候,指定容器持有何种类型,以便在编译期间就能够保证类型的正确性,而不是将错误留到运行的时候
  2. 无论是创建类、方法的时候都可以泛化参数,可以做到代码的复用。

泛型的出现最主要目的为了更好的创建容器类

比如下面这个例子中,在定义Dog类的时候不需要指定其属性a的含义,只需要利用T来表示,在new该对象的时候指定对应的类型即可。

/**
 * 
 * @param <T>
 */
public class Dog<T> {
    private T a;
    public Dog(T a) {
        this.a = a;
    }

    public void set(T a) {
        this.a = a;
    }
    public T get() {
        return a;
    }

    public static void main(String[] args) {
        // 在实例化的时候才确定类型
        Dog<Integer> h3 = new Dog<Integer>(new Integer(3));
        // 然后就可以不用强制类型转化了
        Integer a = h3.get();
        // h3.set("Not an Automobile"); // Error
        // h3.set(1.11); // Error
    }
} 

面试官摸了摸笔记本,心想,回答的还可以,继续追问。

你刚才说泛型消除了类型之间的差异,那你知道jdk底层是如何做到的吗?

大黄:

jdk是通过类型擦除来实现泛型的,编译器在编译之后会擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。比如如果编写 List<String> names = new Arraylist<>();在运行的时候,仅用List来表示。

先不要高兴,面试官可能会继续追问。

面试官:既然你说编译时就已经擦除了,为什么要用类型擦除呢?
大黄:这么做是为了向前兼容,为了兼容jdk1.5以前的程序,确保能和Java 5之前的版本开发二进制类库进行兼容。

好小伙,这都知道,看来之前做足了功课,还得继续问问,试试深浅。

面试官:你知道泛型中的通配符吧,那你说一下什么是限定通配符和非限定通配符。
大黄:限定通配符,是限定类型必须是某一个类型的子类。
比如:List<? extends Fruit> flist表示具有任何从Fruit继承的类型的列表。
而限定通配符:<? super Fruit> 确保类型必须是Fruit的父类来设定类型的下界。

大黄小提醒

不要意味new一个List<? extends Fruit>flist就可以把任何的水果塞到flist里面,很遗憾的告诉你,下面的程序编译不通过的。

/**
 * 泛型中通配符的应用
 */
public class GenericsAndCovariance {
    public static void main(String[] args) {
        // new一个水果篮子
        List<? extends Fruit> flist = new ArrayList<Apple>();
        // 下面编译不会通过
        flist.add(new Apple());
        // 下面编译不会通过
        flist.add(new Orange());
        // 下面编译同样不会通过
        // flist.add(new Fruit());
    }
}

看到这个程序是否感觉到有点懵,我自己创建的水果篮子,我无法往里面放苹果、橙子,甚至连篮子对象也不能放。那我这个篮子还有屁用哦。
是的,确实没有什么用,似乎和预期不太一样。
但是想想也合乎道理。如果不知道list持有什么对象,那么怎么样才能安全地向其中添加对象呢?如果允许了,则获取元素的时候,就会出错。

下面面试继续:

面试官:那你再说说泛型可以用到什么地方?
大黄:泛型即可用用于类定义中、接口的定义、方法的定义、同时也广泛用于容器中。

  1. 在类中使用泛型,可以让类中的属性由泛型定义,而不需要提前知道属性的类型。
  2. 在接口中使用泛型,可以让接口方法的返回值按照各自实现自由定义。这也是常见的工厂设计模式的一种应用。
  3. 在方法中利用泛型,可以做到方法的复用,同一个方法可以传入不同类型参数。实现时只需要将泛型参数列表置于返回值之前即可,JDK1.8中Stream中很多流式方法的定义采用了泛型。

记得加上这句话

我觉得,使用泛型机制最吸引人的地方,在使用容器的地方,比如List、Set、Map,因为在1.5以前,在泛型出现之前,当将一个对象放到容器中,这个对象会默认的转化为Object类型,因此会丢失掉类型信息,可能将一个Apple对象放到Orange容器中,然后试图从Orange容器中获取对象时,得到的是一个Apple,强制转化必然出问题,会抛出RuntimeException,但是有了泛型之后,这种问题在写代码,也就是说在编译期间就会暴露,而不是在运行时暴露。

比如下面定义接口用于生成不同的对象:

/**
 * 定义一个生成器
 * @param <T>
 */
public interface Generator<T> {
    /**
     * 定义生成下一个对象
     * @return
     */
    T next();
}

/**
 * 利用泛型生成器来生成费波列切数
 */
public class Fibonacci implements Generator<Integer> {
    private int count = 0;

    public static void main(String[] args) {
        Fibonacci gen = new Fibonacci();
        for (int i = 0; i < 18; i++)
            System.out.print(gen.next() + " ");
    }
    /**
     * 实现接口的方法,返回值为Integer
     * @return
     */
    public Integer next() {
        return fib(count++);
    }
    /**
     * 定义费波列切数
     * @param n
     * @return
     */
    private int fib(int n) {
        if (n < 2) return 1;
        return fib(n - 2) + fib(n - 1);
    }
}

泛型方法的应用:

public class GenericMethods {

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(gm);
    }

    /**
     * 定义泛型方法
     * @param x         方法的参数为泛型
     * @param <T>
     */
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }
}

回答到这里,关于泛型的问题基本上总结的差不多了。

总结

本身主要围绕开头的几个真正的面试题展开,简单来说,泛型是什么?为什么要有泛型?泛型如何实现的?泛型有哪些用处。
个人观点,一个技术从刚开始学习的时候,从四方面思考,能够事半功倍。

最后大黄分享多年面试心得。面试中,面对一个问题,大概按照总分的逻辑回答即可。先直接抛出结论,然后举例论证自己的结论。一定要第一时间抓住面试官的心里,否则容易给人抓不着重点或者不着边际的印象。

番外

另外,关注大黄奔跑,第一时间收获独家整理的面试实战记录及面试知识点总结。

我是大黄,一个只会写HelloWorld的程序员,咱们下期见。

更多模拟面试

全部评论

(3) 回帖
加载中...
话题 回帖

相关热帖

历年真题 真题热练榜 24小时
技术(软件)/信息技术类
查看全部

近期精华帖

热门推荐