Skip to content

List、Set、Map 详解与遍历

概述

在掌握 集合概述与体系 后,本文深入 ListSetMap 三大常用接口的常用方法遍历方式。List 有序可重复、按索引访问;Set 不重复、无下标;Map 存储键值对、按键取值。日常开发中,除了「增删改查」外,如何安全、高效地遍历集合同样重要——包括 for-each、Iterator 以及 Map 的 keySet/entrySet 等写法。

前置知识

建议已学习 集合概述与体系泛型基础。下文示例均使用泛型(如 List<String>Map<K,V>)。


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 基本操作

java
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"));  // 1

List 的三种遍历方式

方式一:索引 for 循环 — 适合需要下标或要在遍历中按索引修改/删除的场景。

java
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

java
for (String s : list) {
    System.out.println(s);
}

方式三:Iterator(迭代器) — 可在遍历时安全地调用 iterator.remove() 删除当前元素,是「边遍历边删除」的推荐写法。

java
var it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("香蕉")) {
        it.remove();  // 安全删除当前元素
    }
}

提示

若只需顺序访问、不关心下标,优先用 for-each;若要在遍历中删除元素,必须使用 Iteratorremove(),不要用 list.remove(),否则可能触发并发修改异常。


Set 详解

Set 表示不重复元素的集合,没有「第几个」的概念,因此没有 get(index) 这类按索引访问的方法。常用实现类:HashSet(无序)、LinkedHashSet(保持插入顺序)、TreeSet(按自然顺序或比较器排序)。

Set 常用方法

方法说明
add(E e)添加元素,若已存在则不会重复加入,返回是否实际加入
remove(Object o)删除指定元素,返回是否删除成功
contains(Object o)是否包含指定元素
size()元素个数
isEmpty()是否为空
clear()清空

判断「是否已存在」依赖元素的 equalshashCode;若元素是自定义类,须正确重写这两个方法,参见 常用类(equals/hashCode)

示例 2:Set 基本操作与遍历

java
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());    // 2

Set 的遍历:没有索引,只能用 for-eachIterator

java
// 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 基本操作

java
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());               // 1

Map 的四种遍历方式

方式一:遍历 keySet,再 get(key) — 适合主要关心键、且需要多次用键取值的场景;若只关心键,可直接用 keySet 的 for-each。

java
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 — 仅关心值、不关心键时使用。

java
for (Integer value : map.values()) {
    System.out.println(value);
}

方式三:遍历 entrySet(推荐) — 同时需要键和值时,直接遍历 Map.Entry 最清晰,且避免对同一键多次 get

java
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " -> " + value);
}

方式四:Iterator + entrySet — 若要在遍历中删除键值对,应使用迭代器的 remove()

java
var it = map.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, Integer> entry = it.next();
    if (entry.getValue() < 2) {
        it.remove();
    }
}

提示

需要同时使用键和值时,优先用 entrySet 遍历,代码更清晰,且避免重复调用 get(key)


注意事项

  1. 遍历时不要结构性修改
    使用 for-each 或普通 Iterator 遍历时,不要直接调用集合的 add/remove(如 list.remove(o)),否则可能抛出 ConcurrentModificationException。若要「边遍历边删除」,使用 Iteratorremove()

  2. List 的 get/set/remove(index)
    索引必须在 [0, size()) 范围内,否则会抛出 IndexOutOfBoundsException。先判断 index >= 0 && index < list.size() 或做好边界处理。

  3. Map 的 get 与 null
    map.get(key) 在键不存在时返回 null;若值本身允许为 null,无法仅凭 get == null 判断「键是否存在」,应使用 containsKey(key)。需要默认值时用 getOrDefault(key, defaultValue)

  4. Set/Map 与 equals、hashCode
    自定义类作为 Set 的元素或 Map 的键时,必须正确重写 equalshashCode,否则去重和哈希表行为会异常。参见 常用类(Object/equals-hashCode)

  5. subList 是视图
    list.subList(from, to) 返回的是原 List 的视图,对子列表的修改会反映到原列表;且原列表在子列表使用期间不应做结构性修改,否则子列表操作可能抛异常。

注意

ArrayListHashSetHashMap 等常用实现均为非线程安全。多线程下读写同一集合需使用并发容器或外部同步,详见 同步与锁


相关链接

基于 VitePress 构建