泛型:给集合贴个"内容标签"
上面代码里的 List<String>,那个尖括号里的 String 就是泛型。人话:泛型 = 告诉编译器"这个盒子里装的是什么",让编译器帮你把关类型。没有泛型的时代,往 List 里塞了个 Integer,取出来强转成 String,运行时直接炸——泛型就是来治这个病的。
泛型类、泛型方法、通配符 ? extends / ? super
// 泛型类:Box 里装什么类型,由使用时决定
class Box<T> {
private T value;
public void set(T v) { this.value = v; }
public T get() { return value; }
}
public class GenericDemo {
// 泛型方法:类型参数写在返回值前面
public static <T> T first(List<T> list) {
return list.get(0);
}
public static void main(String[] args) {
Box<String> b = new Box<>();
b.set("hello");
String s = b.get(); // 编译器知道是 String,不用强转
}
}
论PECS 原则:? extends 和 ? super 怎么记
这是泛型里最绕的点,背一个口诀:PECS = Producer Extends, Consumer Super(生产者用 extends,消费者用 super)。
? extends T(上界通配):"一切 T 的子类"。你只能往外读(当作 T),不能往里塞(因为不知道具体是哪个子类)。比如方法要"读一组动物"来叫一声,参数写成 List<? extends Animal>,Dog 列表、Cat 列表都能传进来。
? super T(下界通配):"一切 T 的父类"。你只能往里写 T,读出来只能当 Object。典型场景:Collections.copy(dest, src) 的目标列表。
人话记忆:要往外拿数据,用 extends;要往里存数据,用 super。不确定时,先别用通配符,直接写 <T> 泛型方法,更安全。
PECS 完整示例:什么时候用 extends,什么时候用 super
// 类层次:Animal <- Dog <- Puppy
class Animal { String name() { return "动物"; } }
class Dog extends Animal { String name() { return "狗"; } }
class Puppy extends Dog { String name() { return "小狗"; } }
public class PECSDemo {
// —— 生产者:只往外读,用 ? extends Animal ——
// 你可以传 List<Dog>、List<Puppy> 进来,读出来都当 Animal 用
static void printAll(List<? extends Animal> animals) {
for (Animal a : animals) { // 读:OK,编译器知道至少是 Animal
System.out.println(a.name());
}
// animals.add(new Dog()); // 写:编译报错!不知道具体是哪个子类
}
// —— 消费者:只往里写,用 ? super Dog ——
// 你可以传 List<Animal>、List<Object> 进来,往里塞 Dog
static void addDogs(List<? super Dog> dogs) {
dogs.add(new Dog()); // 写:OK,Dog 一定是 ? super Dog 的子类
dogs.add(new Puppy()); // 子类也能写,Puppy 也是 Dog 的子类
// Dog d = dogs.get(0); // 读:编译报错!读出来只能当 Object
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
printAll(dogs); // 读:传 List<Dog> 给 extends Animal 参数
List<Animal> animals = new ArrayList<>();
addDogs(animals); // 写:传 List<Animal> 给 super Dog 参数
}
}
Java 泛型是"编译器的骗局":编译后,List<String> 和 List<Integer> 在运行时都是同一个 List,泛型信息被擦掉了。由此带来两个坑:① 不能 new T()、不能 new T[10](运行时不知道 T 是谁);② list instanceof List<String> 编译不过,只能 list instanceof List。面试爱问,工作中知道"擦除"俩字就够避开大部分坑。
1.(概念题)什么是类型擦除?会带来什么限制?
查看答案
答案:Java 泛型只在编译期检查,编译后泛型信息被擦除,List<String> 和 List<Integer> 运行时都是 List。限制:不能 new T()、不能 new T[10]、不能 instanceof List<String>。
2.(代码题)PECS 口诀是什么?List<? extends Animal> 能读能写吗?
查看答案
答案:Producer Extends, Consumer Super。? extends Animal 只能往外读(当 Animal),不能往里写(不知道具体子类)。? super Dog 只能往里写 Dog,读出来只能当 Object。
3.(代码题)下面这段能编译吗?List<String> list = new ArrayList<Integer>();
查看答案
答案:不能。泛型不协变,ArrayList<Integer> 不是 List<String> 的子类。要表达"什么都行的列表"用 List<?>。
4.(思考题)为什么不能 new T()?怎么绕过?
查看答案
答案:类型擦除后运行时不知道 T 是什么,无法 new。绕过:传个 Class<T> 进来,用 clazz.newInstance() 或反射。