3
我是 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