Java 集合框架:LinkedList、泛型与 Set
LinkedList 用链表换来了头尾操作的极致速度,泛型把类型错误从运行期提前到编译期,增强 for 则是迭代器的语法糖。本篇把这几个看似零散的知识点串成一条线:容器怎么选、遍历怎么写、类型怎么管。
本篇目标
- 理解 LinkedList 的链表结构,说清它和 ArrayList 的取舍
- 看透增强 for 的本质,知道三种遍历方式谁能删除
- 掌握可变参数的写法和坑,认识静态导入
- 理解泛型为什么存在,会写泛型类和泛型方法
LinkedList:链表结构的 List
ArrayList 底层是数组,元素连续排布,按下标查询一步到位;代价是中间增删要整体搬家。LinkedList 反其道而行:每个节点除装数据外还记着前后邻居的地址,像一串手拉手的人。于是查询慢(找第 100 个节点得从头或尾数过去),增删快(摘掉一个节点只要让前后邻居互相牵手),并配齐 addFirst/addLast、getFirst/getLast、removeFirst/removeLast 一套头尾方法。
LinkedList<String> list = new LinkedList<>(); |
什么时候用它:需要频繁在头尾增删的场景,比如实现栈和队列。常见误解是”增删多用 LinkedList”——只有头尾增删它才占优,中间增删得先找到位置,而链表找位置本身就慢;大量随机访问请回 ArrayList。它虽继承了 List 的 get(int index),但链表按下标找人要逐节点数,非必要别在 LinkedList 上玩下标。
用 LinkedList 模拟栈
栈先进后出(像一摞盘子),队列先进先出(像排队买票)。用头尾方法很容易封装一个栈。当然,JDK 已提供现成的 Deque 接口和 ArrayDeque 实现,日常开发直接用它们,自己动手封装只为练手:
public class MyStack { |
增强 for:迭代器的语法糖
JDK 5 的增强 for 把遍历写得极简:
for (String s : list) { |
但它不是新机制——编译后会被还原成迭代器的 hasNext()/next() 循环,对数组则还原成下标循环(如 for (int n : new int[]{1, 2, 3})),纯粹是编译器代写样板代码。由此推出重要结论:增强 for 里不能删除元素——它底层就是迭代器,循环体里 list.remove() 照样触发 fail-fast 抛 ConcurrentModificationException,而它又不给你迭代器的引用,想安全删除都没门。三种遍历能否边遍历边删除:普通 for 可以,但删除后索引要 i-- 回退;迭代器可以,必须用迭代器自己的 remove();增强 for 不能。
可变参数
参数个数不确定时用可变参数:
public static int sum(int... nums) { // 本质是 int[] nums |
sum()、sum(1)、sum(1, 2, 3) 都合法,编译器把实参包成数组传入。注意:一个方法最多一个可变参数且必须放在参数列表最后;方法体内它就是普通数组。顺带一提,Arrays.asList() 能把数组快速转成 List 视图,但这个视图长度固定,增删会抛 UnsupportedOperationException。
静态导入
import static 包名.类名.方法名; 可以把静态方法直接导入到方法级别,之后调用连类名都不用写,比如导入 Collections.sort 后直接 sort(list)。但它有同名冲突的隐患——多个同名静态方法同时导入时分不清谁是谁,反而要加回前缀,所以实战中意义不大,看得懂即可,一般不用。
泛型:把类型错误消灭在编译期
回看没有泛型的年代:
ArrayList list = new ArrayList(); |
泛型是 JDK 5 才加入的特性,此前集合只能以 Object 装万物,取出全靠自觉强转,转错就到运行期才爆炸。泛型要做的是在声明处把元素类型钉死:存错类型编译直接失败,取出也不用强转。可以粗略理解为”把类型当参数传给类或方法”——就像方法有参数,类型也可以参数化。
ArrayList<String> list = new ArrayList<>(); |
好处就两条:提高安全性(错误从运行期提前到编译期),消除强制类型转换。注意 <> 里只能写引用类型,装 int 得写 ArrayList<Integer>。翻看源码会发现 ArrayList 的类声明就是 public class ArrayList<E>,这个 E 就是等你传入的类型参数。
泛型类与泛型方法
泛型定义在类上就是泛型类:
public class Box<T> { |
Box<String> 装字符串、Box<Integer> 装整数,一套代码服务所有类型——JDK 的 ArrayList 就是这么写的。泛型定义在方法上(声明在返回值之前)就是泛型方法:
public static <T> void printAll(T[] arr) { |
泛型还可定义在接口上,配合通配符 ? extends E(E 及其子类)、? super E(E 及其父类)做限定,先混个眼熟即可。
经典练习:去除 ArrayList 中的重复字符串
思路是”新建一个集合当筛子”:遍历原集合,新集合里没有才放进去。
public static ArrayList<String> getSingle(ArrayList<String> list) { |
若元素换成自定义对象,contains 就依赖你有没有重写 equals()——不重写比的是地址,两个内容相同的对象会被当成不同元素。还要注意这个写法是 O(n²):contains 本身要遍历新集合,数据一大就慢,这正是下篇要用 HashSet 优雅判重的原因。
小结
- LinkedList 底层双向链表,头尾增删快、随机访问慢,适合模拟栈和队列;随机读多选 ArrayList。
- 增强 for 是迭代器的语法糖,循环中不能增删;要删就用迭代器自己的
remove()。 - 可变参数本质是数组,一个方法只能有一个且放最后;静态导入能省前缀但易混淆,少用。
- 泛型把元素类型钉在编译期:存错编译失败、取出免强转;泛型类、泛型方法是写通用代码的基础。

