List、Set、Map 详解与遍历
概述
在掌握 集合概述与体系 后,本文深入 List、Set、Map 三大常用接口的常用方法与遍历方式。List 有序可重复、按索引访问;Set 不重复、无下标;Map 存储键值对、按键取值。日常开发中,除了「增删改查」外,如何安全、高效地遍历集合同样重要——包括 for-each、Iterator 以及 Map 的 keySet/entrySet 等写法。
List 详解
List 表示有序、可重复的元素序列,支持按索引访问,是使用频率最高的集合类型。常用实现类为 ArrayList(基于数组,查询快)和 LinkedList(基于链表,头尾增删方便)。
List 常用方法
| 方法 | 说明 |
|---|---|
add(E e) | 在末尾添加元素,返回 true |
add(int index, E e) | 在指定索引处插入元素 |
get(int index) | 获取指定索引处的元素 |
set(int index, E e) | 将指定索引处的元素替换为 e,返回旧值 |
remove(int index) | 删除指定索引处的元素,返回被删元素 |
remove(Object o) | 删除第一个等于 o 的元素(基于 equals),返回是否删除成功 |
size() | 返回元素个数 |
isEmpty() | 判断是否为空 |
contains(Object o) | 是否包含指定元素 |
indexOf(Object o) | 首次出现 o 的索引,不存在返回 -1 |
clear() | 清空所有元素 |
subList(int from, int to) | 返回 [from, to) 的视图(左闭右开) |
示例 1:List 基本操作
import java.util.ArrayList;
import java.util.List;
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add(1, "X"); // 在索引 1 处插入,[A, X, B, C]
String old = list.set(2, "Y"); // 将索引 2 设为 "Y",old 为 "B"
String removed = list.remove(0); // 删除索引 0,removed 为 "A"
boolean ok = list.remove("Y"); // 按值删除,ok 为 true
System.out.println(list); // [X, C]
System.out.println(list.get(0)); // X
System.out.println(list.size()); // 2
System.out.println(list.indexOf("C")); // 1List 的三种遍历方式
方式一:索引 for 循环 — 适合需要下标或要在遍历中按索引修改/删除的场景。
List<String> list = new ArrayList<>(List.of("苹果", "香蕉", "橙子"));
for (int i = 0; i < list.size(); i++) {
System.out.println(i + ": " + list.get(i));
}方式二:增强 for(for-each) — 写法简洁,只读遍历时首选;遍历过程中不能用 list.remove() 等会改变结构的操作,否则可能抛出 ConcurrentModificationException。
for (String s : list) {
System.out.println(s);
}方式三:Iterator(迭代器) — 可在遍历时安全地调用 iterator.remove() 删除当前元素,是「边遍历边删除」的推荐写法。
var it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("香蕉")) {
it.remove(); // 安全删除当前元素
}
}提示
若只需顺序访问、不关心下标,优先用 for-each;若要在遍历中删除元素,必须使用 Iterator 的 remove(),不要用 list.remove(),否则可能触发并发修改异常。
Set 详解
Set 表示不重复元素的集合,没有「第几个」的概念,因此没有 get(index) 这类按索引访问的方法。常用实现类:HashSet(无序)、LinkedHashSet(保持插入顺序)、TreeSet(按自然顺序或比较器排序)。
Set 常用方法
| 方法 | 说明 |
|---|---|
add(E e) | 添加元素,若已存在则不会重复加入,返回是否实际加入 |
remove(Object o) | 删除指定元素,返回是否删除成功 |
contains(Object o) | 是否包含指定元素 |
size() | 元素个数 |
isEmpty() | 是否为空 |
clear() | 清空 |
判断「是否已存在」依赖元素的 equals 与 hashCode;若元素是自定义类,须正确重写这两个方法,参见 常用类(equals/hashCode)。
示例 2:Set 基本操作与遍历
import java.util.LinkedHashSet;
import java.util.Set;
// 使用 LinkedHashSet 保持插入顺序,便于观察
Set<String> set = new LinkedHashSet<>();
set.add("北京");
set.add("上海");
set.add("北京"); // 重复,不会多存
System.out.println(set.add("广州")); // true
System.out.println(set.add("北京")); // false,已存在
System.out.println(set); // [北京, 上海, 广州]
System.out.println(set.contains("上海")); // true
set.remove("北京");
System.out.println(set.size()); // 2Set 的遍历:没有索引,只能用 for-each 或 Iterator。
// for-each
for (String city : set) {
System.out.println(city);
}
// Iterator(若需在遍历中删除,用 iterator.remove())
var it = set.iterator();
while (it.hasNext()) {
String city = it.next();
if (city.startsWith("上")) {
it.remove();
}
}Map 详解
Map 存储键值对(key-value),键不重复,一个键对应一个值;同一键再次 put 会覆盖原值。常用实现类:HashMap(无序)、LinkedHashMap(保持插入/访问顺序)、TreeMap(键有序)。
Map 常用方法
| 方法 | 说明 |
|---|---|
put(K key, V value) | 放入键值对,若键已存在则覆盖并返回旧值,否则返回 null |
get(Object key) | 根据键取值,不存在返回 null |
getOrDefault(Object key, V defaultValue) | 取值,不存在则返回默认值 |
remove(Object key) | 删除指定键的映射,返回被删的值 |
containsKey(Object key) | 是否包含该键 |
containsValue(Object value) | 是否包含该值(需遍历,效率较低) |
size() | 键值对个数 |
isEmpty() | 是否为空 |
clear() | 清空 |
keySet() | 返回所有键组成的 Set 视图 |
values() | 返回所有值组成的 Collection 视图 |
entrySet() | 返回所有「键值对」组成的 Set 视图,元素类型为 Map.Entry<K,V> |
示例 3:Map 基本操作
import java.util.HashMap;
import java.util.Map;
Map<String, Integer> score = new HashMap<>();
score.put("语文", 90);
score.put("数学", 85);
Integer old = score.put("语文", 88); // 覆盖,old 为 90
System.out.println(score.get("语文")); // 88
System.out.println(score.getOrDefault("英语", 0)); // 0(键不存在)
score.remove("数学");
System.out.println(score.containsKey("语文")); // true
System.out.println(score.size()); // 1Map 的四种遍历方式
方式一:遍历 keySet,再 get(key) — 适合主要关心键、且需要多次用键取值的场景;若只关心键,可直接用 keySet 的 for-each。
Map<String, Integer> map = new HashMap<>(Map.of("A", 1, "B", 2, "C", 3));
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " -> " + value);
}方式二:遍历 values — 仅关心值、不关心键时使用。
for (Integer value : map.values()) {
System.out.println(value);
}方式三:遍历 entrySet(推荐) — 同时需要键和值时,直接遍历 Map.Entry 最清晰,且避免对同一键多次 get。
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " -> " + value);
}方式四:Iterator + entrySet — 若要在遍历中删除键值对,应使用迭代器的 remove()。
var it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
if (entry.getValue() < 2) {
it.remove();
}
}提示
需要同时使用键和值时,优先用 entrySet 遍历,代码更清晰,且避免重复调用 get(key)。
注意事项
遍历时不要结构性修改
使用 for-each 或普通 Iterator 遍历时,不要直接调用集合的add/remove(如list.remove(o)),否则可能抛出ConcurrentModificationException。若要「边遍历边删除」,使用 Iterator 的remove()。List 的 get/set/remove(index)
索引必须在[0, size())范围内,否则会抛出IndexOutOfBoundsException。先判断index >= 0 && index < list.size()或做好边界处理。Map 的 get 与 null
map.get(key)在键不存在时返回null;若值本身允许为null,无法仅凭get == null判断「键是否存在」,应使用containsKey(key)。需要默认值时用getOrDefault(key, defaultValue)。Set/Map 与 equals、hashCode
自定义类作为 Set 的元素或 Map 的键时,必须正确重写equals与hashCode,否则去重和哈希表行为会异常。参见 常用类(Object/equals-hashCode)。subList 是视图
list.subList(from, to)返回的是原 List 的视图,对子列表的修改会反映到原列表;且原列表在子列表使用期间不应做结构性修改,否则子列表操作可能抛异常。
注意
ArrayList、HashSet、HashMap 等常用实现均为非线程安全。多线程下读写同一集合需使用并发容器或外部同步,详见 同步与锁。
相关链接
- 集合概述与体系 — 集合框架整体与 List/Set/Map 选型
- 泛型在集合中的使用与常见陷阱 — 泛型与类型擦除在集合中的注意点
- Collections 工具类 — 排序、查找、不可变集合等
- 常用类(Object/equals-hashCode) — 自定义类作为 Set 元素或 Map 键的前提
- Oracle Java 教程 - 集合