一、集合的遍历方式(全解析)
Java 集合框架中,不同类型的集合(List
、Set
、Map
)遍历方式略有差异,但核心目标是访问集合中的每个元素。以下按集合类型分类讲解所有遍历方式,包含语法、适用场景和注意事项。
1. Collection
体系(List
、Set
)的遍历方式
Collection
是单元素集合的根接口(List
和 Set
都继承它),通用遍历方式如下:
(1)增强 for 循环(foreach
)
语法:
for (元素类型 变量名 : 集合) {
// 操作变量
}
适用场景:
- 仅需读取元素,无需修改集合结构(添加/删除元素)。
- 所有
Collection
实现类(List
、Set
均可)。
优点:代码简洁,可读性高。
缺点:
- 无法获取元素索引(
List
也不行)。 - 遍历中不能修改集合结构(添加/删除元素),否则会抛出
ConcurrentModificationException
。
示例:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ForEachDemo {
public static void main(String[] args) {
// List遍历
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
System.out.println("List遍历:");
for (String s : list) {
System.out.println(s);
}
// Set遍历
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
System.out.println("Set遍历:");
for (int num : set) {
System.out.println(num);
}
}
}
(2)迭代器(Iterator
)
语法:
Iterator<元素类型> it = 集合.iterator();
while (it.hasNext()) { // 判断是否有下一个元素
元素类型 变量 = it.next(); // 获取下一个元素
// 操作变量
}
适用场景:
- 需要在遍历中删除元素(唯一安全的方式)。
- 所有
Collection
实现类(List
、Set
均可)。
优点:
- 支持遍历中安全删除元素(通过
it.remove()
,而非集合的remove()
)。 - 可控制遍历过程(如提前终止)。
缺点:代码相对繁琐。
示例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("B")) {
it.remove(); // 安全删除当前元素(不会抛异常)
}
}
System.out.println("删除后集合:" + list); // [A, C]
}
}
注意:
- 调用
it.next()
前必须先调用it.hasNext()
,否则可能抛出NoSuchElementException
。 - 遍历中若通过集合的
remove()
方法删除元素(而非it.remove()
),会触发ConcurrentModificationException
。
(3)普通 for 循环(仅适用于 List
)
语法:
for (int i = 0; i < 集合.size(); i++) {
元素类型 变量 = 集合.get(i); // 通过索引获取元素
// 操作变量
}
适用场景:
- 仅
List
集合(因List
有序且有索引,Set
无序无索引,不支持)。 - 需要获取元素索引,或需要反向遍历(从后往前)。
优点:
- 可直接获取索引,方便定位元素。
- 支持修改集合结构(如删除元素后调整索引)。
缺点:仅适用于 List
,Set
无法使用。
示例:
import java.util.ArrayList;
import java.util.List;
public class ForLoopDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
// 正向遍历
System.out.println("正向遍历:");
for (int i = 0; i < list.size(); i++) {
System.out.println("索引" + i + ":" + list.get(i));
}
// 反向遍历
System.out.println("反向遍历:");
for (int i = list.size() - 1; i >= 0; i--) {
System.out.println("索引" + i + ":" + list.get(i));
}
}
}
(4)ListIterator
(仅适用于 List
,双向遍历)
ListIterator
是 Iterator
的子接口,仅 List
支持,可双向遍历(向前/向后)和添加元素。
语法:
ListIterator<元素类型> lit = 列表.listIterator();
// 向后遍历
while (lit.hasNext()) {
元素类型 变量 = lit.next();
}
// 向前遍历(需先向后遍历到末尾)
while (lit.hasPrevious()) {
元素类型 变量 = lit.previous();
}
适用场景:
List
集合需要双向遍历(如先向后再向前)。- 需要在遍历中添加元素(
lit.add(元素)
)。
示例:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
ListIterator<String> lit = list.listIterator();
// 向后遍历并添加元素
while (lit.hasNext()) {
String s = lit.next();
if (s.equals("B")) {
lit.add("C"); // 在B之后添加C
}
}
System.out.println("添加后集合:" + list); // [A, B, C]
// 向前遍历
System.out.println("向前遍历:");
while (lit.hasPrevious()) {
System.out.println(lit.previous()); // C → B → A
}
}
}
(5)Java 8+ forEach
方法(结合 Lambda)
Collection
接口在 Java 8 中新增了 forEach(Consumer<? super T> action)
方法,可通过 Lambda 表达式遍历,代码更简洁。
语法:
集合.forEach(元素 -> {
// 操作元素
});
适用场景:
- 仅需读取元素,无需修改集合结构。
- 所有
Collection
实现类(List
、Set
均可)。 - 希望代码更简洁,适合函数式编程风格。
优点:代码最简洁,一行搞定。
缺点:遍历中不能修改集合结构(否则抛异常),且无法直接获取索引。
示例:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ForEachLambdaDemo {
public static void main(String[] args) {
// List遍历
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
System.out.println("List遍历:");
list.forEach(s -> System.out.println(s));
// Set遍历
Set<Integer> set = new HashSet<>();
set.add(100);
set.add(200);
System.out.println("Set遍历:");
set.forEach(num -> System.out.println(num));
}
}
2. Map
体系的遍历方式
Map
存储键值对(key-value
),遍历方式需围绕 key
、value
或键值对(Entry
)展开。
(1)遍历 key
集,再获取 value
步骤:
- 用
keySet()
获取所有key
的Set
集合。 - 遍历
key
集,通过get(key)
获取对应value
。
适用场景:需要同时使用 key
和 value
,但 key
更易遍历。
示例:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapKeySetDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Java", 90);
map.put("Python", 85);
// 获取key集
Set<String> keys = map.keySet();
// 遍历key集
for (String key : keys) {
Integer value = map.get(key);
System.out.println(key + " → " + value);
}
}
}
(2)遍历 value
集(仅需 value
时)
步骤:用 values()
获取所有 value
的 Collection
集合,直接遍历。
适用场景:仅需要 value
,无需 key
。
示例:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class MapValuesDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Java", 90);
map.put("Python", 85);
// 获取value集
Collection<Integer> values = map.values();
// 遍历value集
for (int score : values) {
System.out.println("分数:" + score);
}
}
}
(3)遍历键值对(Entry
集,推荐)
Map
中的每个键值对由 Map.Entry<K, V>
对象表示,通过 entrySet()
获取所有 Entry
的 Set
集合,直接遍历键值对,效率最高(无需二次查询 value
)。
适用场景:需要同时操作 key
和 value
(推荐优先使用)。
示例:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapEntrySetDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Java", 90);
map.put("Python", 85);
// 获取Entry集
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// 遍历Entry集
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " → " + value);
}
}
}
(4)Java 8+ forEach
方法(结合 Lambda)
Map
接口在 Java 8 中新增 forEach(BiConsumer<? super K, ? super V> action)
方法,直接通过 Lambda 遍历键值对。
语法:
map.forEach((key, value) -> {
// 操作key和value
});
适用场景:需要简洁代码同时操作 key
和 value
。
示例:
import java.util.HashMap;
import java.util.Map;
public class MapForEachLambdaDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Java", 90);
map.put("Python", 85);
// Lambda遍历键值对
map.forEach((key, value) -> {
System.out.println(key + " → " + value);
});
}
}
二、集合的选择:什么时候用哪种集合?
集合的选择需根据业务需求(如是否有序、是否去重、操作效率等)决定,核心参考以下维度:
1. 先确定集合类型:List
、Set
还是 Map
?
List
:需要有序、可重复的元素(如列表、数组扩展)。Set
:需要无序、不可重复的元素(如去重、唯一标识)。Map
:需要键值对映射(如字典、缓存、索引表)。
2. List
实现类的选择
实现类 | 底层结构 | 核心特点 | 适用场景 |
---|---|---|---|
ArrayList |
动态数组 | 查询快(O(1) ),增删慢(中间位置 O(n) ) |
频繁查询、少量增删(如用户列表、商品列表) |
LinkedList |
双向链表 | 增删快(首尾 O(1) ),查询慢(O(n) ) |
频繁增删(如队列、栈、链表结构) |
3. Set
实现类的选择
实现类 | 底层结构 | 核心特点 | 适用场景 |
---|---|---|---|
HashSet |
哈希表 | 无序,去重,增删查效率高(O(1) ) |
仅需去重,无需排序(如用户ID集合) |
TreeSet |
红黑树 | 有序(自然排序/自定义排序),去重 | 去重且需要排序(如排行榜、按规则排序的唯一元素) |
4. Map
实现类的选择
实现类 | 底层结构 | 核心特点 | 适用场景 |
---|---|---|---|
HashMap |
哈希表 | key无序,增删查效率高(O(1) ),非线程安全 |
一般键值对存储(如缓存、配置映射) |
TreeMap |
红黑树 | key有序(自然排序/自定义排序) | 需要按key排序的键值对(如按日期排序的日志) |
ConcurrentHashMap |
哈希表(分段锁) | 线程安全,高效并发,key无序 | 多线程环境下的键值对存储 |
三、集合使用的最佳实践
- 优先选择具体实现类:声明集合时用接口(如
List<String> list = new ArrayList<>()
),便于后续替换实现类。 - 初始化时指定容量:如
new ArrayList<>(100)
,避免频繁扩容(哈希表默认容量16,负载因子0.75)。 - 遍历方式选择:
- 仅读取:用
forEach
(Lambda)最简洁。 - 需要删除元素:用
Iterator
(List
也可用普通for循环,但需注意索引调整)。 - 需要索引:
List
用普通for循环。 Map
优先用entrySet()
遍历键值对(效率最高)。
- 仅读取:用
- 避免集合嵌套过深:如
Map<String, List<Map<String, Object>>>
会降低可读性,建议用实体类封装。 - 线程安全考量:多线程场景下,
ArrayList
/HashMap
需替换为CopyOnWriteArrayList
/ConcurrentHashMap
(而非Collections.synchronizedList()
,效率更高)。
总结
- 遍历方式:根据集合类型(
List
/Set
/Map
)和需求(是否修改、是否需要索引)选择,Iterator
适合删除元素,forEach
(Lambda)适合简洁读取,entrySet()
是Map
最高效的遍历方式。 - 集合选择:根据“有序性”“重复性”“操作效率”决策——
List
有序可重复,Set
无序去重,Map
键值对映射;具体实现类按查询/增删效率、排序需求选择。
掌握这些规则,能写出高效、易维护的集合操作代码。
如需巩固本文内容,可点击以下链接完成相关练习:点击此处进入练习题。