Java泛型通配符详解:搞懂?/extends/super用法,避开集合操作踩坑点
上次跟你们聊了泛型的基础用法,今天接着往下说 —— 泛型里还有个挺重要的概念叫 “通配符”,就是那个问号 “?”,很多人第一次见都懵:这玩意儿跟普通泛型有啥区别?为啥有时候非得用它不可?小索奇当初也卡这儿好久,后来拿实际例子一琢磨,才彻底明白它的用处。
先给你出个题:假如你写了个方法,要打印所有 List 集合里的元素,不管这个 List 装的是 String、Integer 还是别的类型,该咋写?
你可能会想,直接用 List不就行了?比如这样:
public static void printList(List list) {
for (T item : list) {
System.out.println(item);
}
}
这么写没问题,但还有个更简洁的方式 —— 用通配符 “?”:
public static void printList (List<?> list) {
for (Object item : list) {
System.out.println (item);
}
}
看出区别没?用通配符的话,不用定义泛型参数,代码更短。而且这两种写法效果一样,不管你传 List还是 List,都能正常打印。那为啥要有两种写法呢?其实核心区别在于:如果方法里不需要用到泛型的具体类型,只用通配符就够了;要是需要操作具体类型(比如给集合加元素),就得用。
比如你想写个方法,给 List 里加一个默认元素,这时候用通配符就不行了:
// 这段代码会编译报错!
public static void addDefault (List<?> list) {
list.add ("默认值"); // 报错:无法确定?的类型,不能加 String
}
这背后的原因是啥呢?因为通配符 “?” 代表 “未知类型”,编译器不知道这个 List 到底装的是啥,自然不敢让你随便加元素 —— 万一人家是 List,你硬塞个 String 进去,不就乱套了?
但用就能搞定:
public static void addDefault(List list, T defaultValue) {
list.add(defaultValue);
}
调用的时候指定类型就行,比如给 List加默认值:
List strList = new ArrayList<>();
addDefault (strList, "默认值"); // 没问题
是不是一下子就懂了?简单说就是:通配符适合 “只读” 场景(比如打印集合),泛型参数适合 “读写” 场景(比如给集合加元素)。
除了单纯的 “?”,通配符还有两种常用写法:<? extends T > 和 <? super T>,这俩也特容易搞混,小索奇当初记了好几天才分清。
先看 <? extends T>,它代表 “T 及其子类”。比如 <? extends Number>,就包括 Integer、Double、Long 这些 Number 的子类。这种写法的特点是 “能读不能写”(跟普通?类似,但多了类型限制)。比如你写个方法,求所有 Number 类型集合的总和:
public static double sum (List<? extends Number> list) {
double total = 0;
for (Number num : list) {
total += num.doubleValue ();
}
return total;
}
不管你传 List还是 List,都能算总和,因为它们都是 Number 的子类。但还是不能往里面加元素,比如 list.add (10) 会报错 —— 因为编译器不知道具体是 Integer 还是 Double,怕加错类型。
再看 <? super T>,它代表 “T 及其父类”。比如 <? super Integer>,就包括 Number、Object 这些 Integer 的父类。这种写法的特点是 “能写不能读”(或者说读出来是 Object 类型)。比如你想写个方法,给 List 里加多个 Integer 元素:
public static void addIntegers (List<? super Integer> list) {
list.add (1);
list.add (2); // 没问题,因为?至少是 Integer 类型
}
调用的时候,传 List、List甚至 List都能行。但读元素的时候,只能用 Object 接收:
for (Object obj : list) {
// 要想用 Integer,得手动强转
Integer num = (Integer) obj;
}
小索奇之前做统计功能时,就用 <? super Integer> 存过数据 —— 既可以存到 Integer 列表,也能存到 Number 列表,灵活度特别高。
最后给你总结个小口诀,记起来更方便:“上界 extends 能读不能写,下界 super 能写不能读,通配符?只读不写,泛型读写都能搞”。
你们平时写代码时,有没有用过通配符却踩了坑的?比如想加元素加不进去,或者不知道该用 extends 还是 super?可以在评论区跟小索奇聊聊,咱们一起把泛型这点事儿彻底搞明白~
搜索即兴小索奇,点击关注,加入社区群聊,获取更多好用工具和资源