Java 集合框架:List 与迭代器
数组能存数据,但长度一旦 new 出来就改不了,而真实开发中数据量往往事先未知,于是 Java 提供了一整套可伸缩的集合框架。本篇作为系列开篇,讲清 Collection 体系的顶层设计、Iterator 迭代器这一统一的遍历抽象,以及最常用的 ArrayList。
本篇目标
- 说清数组与集合的区别,知道什么场景用谁
- 看懂 Collection 体系,记住根接口的常用方法
- 掌握 Iterator 遍历,初步认识 fail-fast
- 熟练使用 ArrayList 的增删改查
为什么数组不够用
需求:存 3 个学生对象并逐个打印。用数组很自然:
Student[] arr = new Student[3]; |
问题在于 new Student[3] 把容量写死了:第 4 个学生来了,只能 new 一个更长的数组再逐个拷贝。数组与集合的核心区别有两点:一是长度,数组定长,集合随元素增减自动伸缩;二是存储类型,数组能存基本类型也能存对象,集合只存对象(存基本类型会自动装箱成 Integer 等包装类)。注意它们存对象时存的都是引用(地址值)。
什么时候用谁:元素个数固定且已知用数组,简单又省内存;个数不定、需要频繁增删用集合。
Collection 体系:先看懂这张地图
集合框架的根是两大接口:Collection(单列,一个一个存)和 Map(双列,一对一对存)。Collection 往下主要分两支:List 有序、有索引、可重复,代表实现 ArrayList、LinkedList;Set 无索引、不重复,代表实现 HashSet、TreeSet。”有序”指存取顺序一致,不是按大小排序。层级设计的意义在于共性方法不断向上抽取到 Collection 接口——无论底层是数组还是链表,你都用同一套 API,这就是面向接口编程的好处。
Collection 的常用操作
Collection<String> c = new ArrayList<>(); |
注意 toArray() 返回 Object[],取出后想调子类特有方法需要强转。contains、remove 底层都依赖元素的 equals(),自定义类别忘了重写——这个伏笔讲 HashSet 时会彻底展开。
除单个操作外还有一组带 All 的批量方法:addAll(c) 把另一个集合全部并入,removeAll(c) 求差集,retainAll(c) 求交集,containsAll(c) 判断是否整体包含。合并数据时批量方法比逐个 add 简洁得多。
Iterator:统一的遍历抽象
每种集合的存储结构不同,若各自暴露一套遍历方式,使用者就得挨个学。框架的做法是把遍历抽象成 Iterator 接口:hasNext() 问”还有没有”,next() 取”下一个”,每个集合用私有内部类(如 ArrayList 的 Itr)实现它,对外只暴露 iterator() 方法。遍历逻辑与存储结构解耦,这就是面向接口编程。
Iterator<String> it = c.iterator(); |
想验证这套机制,可以按住 Ctrl 点进 ArrayList.iterator():返回值是 new Itr(),而 Itr 正是 ArrayList 的私有内部类,重写了 Iterator 的全部抽象方法——每个集合都把”怎么遍历”藏在自己肚子里。
两个使用细节:迭代器是一次性的,走完想再遍历要重新获取;循环体里 next() 每轮只能调一次,调两次会跳过元素甚至抛 NoSuchElementException。
List 与 ArrayList
List 补充了索引这把钥匙,多出一批带下标的特有方法:
List<String> list = new ArrayList<>(); |
ArrayList 底层是动态数组:查询按下标一步到位,快;中间增删要挪动后续所有元素,慢。”动态”体现在自动扩容——元素装满时,内部会创建一个约 1.5 倍长的新数组并把旧元素整体拷贝过去,所以预估好容量能减少扩容开销。它是日常开发默认的第一选择。兄弟 Vector 是古老的线程安全版,方法挂满 synchronized,效率低,新代码不用。另外 List 还有个增强版迭代器 ListIterator,多出 hasPrevious()/previous(),支持逆着往回遍历。更老的 Vector 还能用 Enumeration 的 hasMoreElements()/nextElement() 遍历,那是 JDK 1.0 时代的遗产,认识即可。
常见错误:边遍历边删改
经典陷阱:遍历中发现 “world” 就顺手 add:
Iterator<String> it = list.iterator(); |
ArrayList 内部维护 modCount(修改计数),创建迭代器时记下当时的值,之后每次 next() 都校验两边是否一致;绕开迭代器直接 list.add(),计数对不上,迭代器立刻 fail-fast 报错——宁可立刻崩溃,也不在状态混乱时给出不可靠结果。这是集合框架的重要设计哲学,后面看源码还会再见;下一篇讲的增强 for 底层同样是迭代器,也受这条约束。正确做法是用迭代器自己的方法改:
ListIterator<String> lit = list.listIterator(); |
经典练习:存储自定义对象并遍历
List<Student> list = new ArrayList<>(); |
三种方式结果一致,按场景取舍:要下标用普通 for,要边遍历边删用迭代器,图省事用增强 for。
小结
- 数组定长、可存基本类型,集合变长、只存对象;个数固定用数组,否则用集合。
- Collection 是单列集合的根接口:List 有序可重复,Set 不重复;带 All 的方法做批量运算。
- Iterator 把遍历统一成
hasNext()+next();迭代器一次性,next()每轮只调一次。 - 遍历中改集合必须用迭代器自身的方法,否则 fail-fast 抛
ConcurrentModificationException。 - ArrayList 底层动态数组,查询快、增删慢,是默认容器。

