我是 javapub,一名 Markdown 程序员从‍,八股文种子选手。

面试官:小伙子,说实话,泛型这个机制一开始我也是一头雾水,搞不太了解它到底要处理什么问题。你能不能不那么书呆子,给我普普通通地讲一讲泛型?

提名人: 好嘞,咱们来聊聊泛型。首要,泛型要处理的最首要的问题便是类型不安全。比方说,你有一个箱子,能够装任何东西:

public class Box {
    private Object obj;
    public void set(Object obj) {
        this.obj = obj;
    }
    public Object get() {
        return obj;
    }
}

然后你用它装了一个苹果:

Box b = new Box();
b.set(new Apple());

但是当你取出来的时分,是一个水果啊,你不知道是苹果仍是香蕉,需求强转类型:

Apple a = (Apple) b.get();  // 强转,可能呈现ClassCastException

这便是类型不安全,一旦强转错了类型,程序就GG了。 泛型来了之后,状况就不一样了。咱们能够这样界说箱子:

public class Box<T> {  // <T>便是类型参数
    private T obj;
    public void set(T obj) {
        this.obj = obj;
    }
    public T get() {
        return obj;
    }
}

然后在用的时分,指定T的实践类型,比方:

Box<Apple> b = new Box<Apple>();
b.set(new Apple());
Apple a = b.get();  // 不需求强转,类型安全!

所以泛型最大的优点便是让代码类型安全,不再需求强制类型转化,防止ClassCastException异常,让代码更强健。它把类型检查的工作从运转时提早到了编译时。

面试官:哇,原来如此!解说的真的很通俗易懂,我都了解了!那泛型中最简单搞混的两个概念是什么?

面试官:最简单搞混的两个概念,应该是类型参数和实践类型参数吧?

提名人: 对的,这两个概念简单混杂。咱们再举个例子:

public class Box<T> {   // <T>便是类型参数
    private T obj;
}
Box<Apple> b = new Box<>();   // Apple便是实践类型参数

类型参数T是在界说泛型类Box时运用的,代表一个不知道的类型。咱们不知道运用者会替换成什么类型,所以用T表明。 而实践类型参数Apple是在实例化Box时实践替换类型参数T的类型。它给T一个清晰的类型,用于这次实例化。 所以类型参数是个不知道的类型占位符,实践类型参数是替换类型参数的具体类型。了解了这两个概念的区别,泛型的很多当地就不会再混杂了。

面试官:说的太好了,我都不好意思问你其他的了!那最后两点疑问,1)为啥泛型类不能有静态办法?2)类型擦除是干嘛的?

提名人: 好的,两个很好的疑问: 1)泛型类不能有静态办法的原因是由于静态办法在类加载的时分就被创立,而泛型类在实例化的时分才能确认类型参数的实践类型。这时分静态办法已经创立完了,无法运用这个实践类型,所以编译器不答应这么做。 2)类型擦除便是编译器删去一切与类型参数相关的信息,并替换为上限(通常是Object类型)的进程。由于Java在1.5之前并没有泛型的概念,所以编译器会把一切的泛型类型全部擦除掉,在运转时期间不会存在任何泛型类型的参数信息。这也是为什么泛型类不能有根本类型的参数的原因。 类型擦除有利有弊,优点是能够在1.5之前的VM上运转泛型代码,害处是导致少许运转期间的效率损失,由于擦除后一切的类型参数都被替换为Object类型。不过这点性能损失在大部分状况下能够忽略。

面试官:太棒了,你的解说简直让人眼前一亮!真的学到很多,谢谢你的精彩解说!

提名人: 谢谢面试官的夸奖,我也在这个进程中对泛型有了更深的了解,十分高兴能与你进行这次交流与讨论。

面试官:在聊了泛型这么多后,还有些细节想问一下:

1. 泛型中<?>和<? extends T>别离代表什么含义?

提名人: <?>代表一个不知道类型的通配符,能够用在类型参数的方位,表明接受任何类型。比方:

public void print(Box<?> box) {
    ...
}

这个办法能够传递任何类型的Box进来,由于<?>能够匹配任何类型。 而<? extends T>表明从T类型到其子类型之间的某种类型,它代表的上界类型可能是T,也可能是T的子类型。比方:

public void print(Box<? extends Fruit> box) {
    ...
}

这个办法能够传递Box或许Box进来,由于Apple和Orange都是Fruit的子类。但不能传Box,由于Fruit的子类型不包括Object。 所以<?>表明全类型通配,而<? extends T>表明从T到子类型的规模内的某种类型,具有上界的语义。

2. 泛型办法和泛型类有什么区别?

泛型办法是在普通类中界说带类型参数的办法,而泛型类是在界说类本身时指定类型参数。比方: 泛型办法:

public class Box {
    public <T> void print(T t) {
        ...
    }
} 

泛型类:

public class Box<T> {
    private T t;
    ... 
}

首要区别在于泛型类的类型参数能够用在整个类的办法和属性上,而泛型办法的类型参数只在这个办法内有效。泛型办法更灵敏,能够在非泛型类上运用。 除此之外,泛型办法能够有static修饰符,能够在静态办法内运用类型参数。而泛型类不能有静态办法和静态属性,原因和前面说的类型擦除有关。

3. 泛型的上下限是什么?运用场景又是什么?

泛型的上限是<? extends T>,表明从T到子类型的规模;下限是<? super T>,表明从T到父类型的规模。 上限的运用场景是当需求获取T的子类型目标时,比方从集合中取出元素。下限的运用场景是当需求增加T的父类型目标时,比方往集合中增加元素。

Box<? extends Fruit> box1;   // 放入Apple、Orange等
box1.add(new Apple());      // 只能增加Fruit的子类型  
Box<? super Fruit> box2;    // 放入Fruit、Food等 
box2.add(new Food());      // 只能增加Fruit的父类型   

所以上下限首要是为了在广泛束缚类型的同时,也答应满足某些运用场景的需求,使得泛型愈加灵敏有用。

面试官:泛型真的有些杂乱,但你解说的很通俗易懂,我都差不多了解了。最后两个小问题:

1. 泛型中的鸿沟是干嘛的?

鸿沟是对类型参数指定的束缚,意图是束缚类型参数能被替换的实践类型。比方,咱们能够这样界说一个泛型办法:

public <T extends Number> void print(T t) {
    System.out.println(t.intValue());
}

这儿咱们指定T有必要是Number或其子类型,假如调用时用String类型替换T,则会编译过错,由于String不符合束缚。 鸿沟有两种形式:

  • 类名或许接口名,例如T extends Number,表明T有必要是Number类型或其子类
  • 另一个类型参数,例如<T, S extends T>,表明S有必要是T或其子类型 所以鸿沟的作用便是束缚类型参数能够替换的实践类型,确保在办法中能够正常运用某些操作,防止由于替换过错类型导致的运转过错。

2. 泛型中通配符和无鸿沟的有什么区别?

无鸿沟的表明任何类型,它没有任何束缚,能够了解为,T能够替换为任何类型。 而通配符有些微的区别,它表明“不知道类型”,也没有具体的类型鸿沟,但它只能在“读”的场景运用,不能在“写”的场景运用。由于编译器无法确认它到底是哪种类型。 举个例子:

public void print(Box< ?> box) {  // 读操作,ok
    ...
}
public void add(Box< ?> box, Object o) { // 写操作,编译过错
    box.set(o);  
}

所以无鸿沟的能够呈现在读和写的操作中,而通配符只能在读操作场景运用,这是两者的首要区别。通常在像泛型办法的界说中,运用无鸿沟的会更灵敏,而在一些读操作的泛型办法中,运用通配符能够更广泛的匹配不同的Box类型。

面试官:真是一个很细致的区别,我以前也常常搞不清这两者的差别,你的解说让我受益匪浅!谢谢你将这些泛型的概念解说的如此清晰和深入,我对泛型也有了更全面的认识。真是一个十分愉快的交流进程!

提名人: 十分高兴能帮到您!我自己在预备和答复的进程中,也对泛型有了更深入的了解,这种问答的形式确实是学习的好办法。谢谢面试官的精彩问题,让这个进程变得十分有价值。我也十分欣赏这次交流,收成颇丰,祝面试官有一个美好的一天!

最近我在更新《面试1v1》系列文章,首要以场景化的方式,解说咱们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣能够重视JavaPub追更!

《面试1v1》java泛型

目录合集:

Gitee:https://gitee.com/rodert/JavaPub

GitHub:https://github.com/Rodert/JavaPub