【016】集合框架总览:List/Set/Map 与线程安全

写业务代码时,你可能天天都在用 ListMapSet,但有没有想过:ArrayList 和 LinkedList 有什么区别HashMap 底层是怎么实现的为什么 HashSet 存不了重复元素多线程环境下该用哪个集合

集合是 Java 开发中最常用的工具之一,选对集合能让代码更简洁、性能更高;选错集合则可能导致性能问题甚至 bug。这篇帮你把集合框架彻底搞明白,遇到「要不要用 LinkedList」「HashMap 够不够安全」这类问题时不再纠结。下面我按「Collection 接口 → List → Set → Map → 线程安全集合 → 工具类」的顺序往下聊。


1. 集合框架全景图 🗺️

1.1 集合框架核心接口

Java 集合框架(Java Collections Framework)主要包含以下接口:

text 复制代码
Collection(集合根接口)
│
├─ List(有序、可重复)
│  ├─ ArrayList(数组实现)
│  ├─ LinkedList(链表实现)
│  └─ Vector(同步 ArrayList)
│
├─ Set(无序、去重)
│  ├─ HashSet(哈希表实现)
│  ├─ LinkedHashSet(哈希表 + 链表,保持插入顺序)
│  └─ TreeSet(红黑树实现,排序)
│
└─ Queue(队列)
   ├─ LinkedList(实现 Queue 接口)
   └─ PriorityQueue(优先级队列)

Map(键值对根接口)
├─ HashMap(哈希表实现)
├─ LinkedHashMap(哈希表 + 链表,保持插入顺序)
├─ TreeMap(红黑树实现,排序)
├─ Hashtable(同步 HashMap)
└─ ConcurrentHashMap(并发 HashMap)

1.2 集合 vs 数组

特性 数组 集合
大小固定 ✅ 固定 ✅ 可动态增长
类型固定 ✅ 固定 ✅ 泛型支持
性能 高(连续内存) 较低(需要扩容)
功能 基础 丰富(排序、去重等)
存储基本类型 ✅ 可以 ❌ 包装为对象
java 复制代码
// 数组:大小固定,类型固定
String[] arr = new String[10];
arr[0] = "hello";

// 集合:大小可变,泛型
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");

2. List:有序、可重复 📋

2.1 ArrayList vs LinkedList

特性 ArrayList LinkedList
底层实现 动态数组 双向链表
随机访问 O(1) O(n)
头部插入/删除 O(n) O(1)
尾部插入/删除 均摊 O(1) O(1)
内存占用 小(只需数组) 大(需要节点+指针)
线程安全
java 复制代码
// ArrayList:适合随机访问、尾部操作
List<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.get(0);  // O(1),快

// LinkedList:适合头部操作、顺序访问
List<String> linkedList = new LinkedList<>();
linkedList.addFirst("a");  // O(1),快
linkedList.get(0);  // O(n),慢

2.2 什么时候用 ArrayList?

  • 随机访问多:需要通过索引频繁获取元素
  • 尾部操作多:主要在列表末尾添加/删除元素
  • 内存敏感:希望占用更少内存
java 复制代码
// 场景:存储用户列表,需要按索引查询
List<User> users = new ArrayList<>();
for (int i = 0; i < users.size(); i++) {
    User user = users.get(i);  // O(1),快
}

2.3 什么时候用 LinkedList?

  • 头部操作多:频繁在列表开头添加/删除元素
  • 顺序遍历多:主要通过迭代器遍历
  • 不需要随机访问:不需要通过索引获取元素
java 复制代码
// 场景:实现队列(FIFO)
Queue<String> queue = new LinkedList<>();
queue.offer("a");  // 入队
queue.offer("b");
String head = queue.poll();  // 出队,O(1)

2.4 Vector:线程安全的 ArrayList

Vector 是 synchronized 的,但性能较差,现在很少用:

java 复制代码
// Vector 内部方法都用 synchronized 修饰
public synchronized boolean add(E e) { ... }
public synchronized E get(int index) { ... }

替代方案 :用 Collections.synchronizedList() 包装,或者用 CopyOnWriteArrayList

java 复制代码
// 方式 1:Collections.synchronizedList
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// 方式 2:CopyOnWriteArrayList(读多写少场景)
List<String> cowList = new CopyOnWriteArrayList<>();

2.5 ArrayList 底层实现

ArrayList 底层是一个动态数组

java 复制代码
// ArrayList 核心字段
public class ArrayList<E> {
    private static final int DEFAULT_CAPACITY = 10;
    private Object[] elementData;  // 存储元素的数组
    private int size;  // 实际元素个数
}

扩容机制

java 复制代码
// 添加元素时,如果数组满了,会扩容
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 1.5 倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    elementData = Arrays.copyOf(elementData, newCapacity);
}

扩容是有代价的 ,所以建议预估容量

java 复制代码
// ❌ 频繁扩容
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
    list.add("item" + i);  // 可能触发多次扩容
}

// ✅ 预估容量
List<String> list = new ArrayList<>(20000);
for (int i = 0; i < 10000; i++) {
    list.add("item" + i);
}

3. Set:无序、去重 🎯

3.1 HashSet vs LinkedHashSet vs TreeSet

特性 HashSet LinkedHashSet TreeSet
底层实现 哈希表 哈希表 + 链表 红黑树
顺序 无序 保持插入顺序 排序(自然顺序/自定义)
查找/添加 O(1) O(1) O(log n)
线程安全
允许 null 1 个 1 个 否(TreeMap 特性)
java 复制代码
// HashSet:无序、去重
Set<String> hashSet = new HashSet<>();
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("apple");  // 重复,不会添加
System.out.println(hashSet);  // [banana, apple](顺序不确定)

// LinkedHashSet:保持插入顺序
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("apple");
linkedHashSet.add("banana");
linkedHashSet.add("cherry");
System.out.println(linkedHashSet);  // [apple, banana, cherry]

// TreeSet:排序
Set<String> treeSet = new TreeSet<>();
treeSet.add("cherry");
treeSet.add("apple");
treeSet.add("banana");
System.out.println(treeSet);  // [apple, banana, cherry]

3.2 HashSet 底层实现

HashSet 底层是 HashMap(用 map 的 key 存数据,value 存固定值):

java 复制代码
// HashSet 内部就是一个 HashMap
public class HashSet<E> {
    private transient HashMap<E, Object> map;
    
    public HashSet() {
        map = new HashMap<>();
    }
    
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;  // PRESENT 是固定值
    }
}

去重原理:HashMap 的 key 不能重复,所以 HashSet 也不能重复。

3.3 什么时候用 HashSet?

  • 去重:需要去除重复元素
  • 快速查找:需要判断元素是否存在
  • 不关心顺序:只要唯一性
java 复制代码
// 场景:统计不重复的用户 ID
Set<Long> userIds = new HashSet<>();
for (Order order : orders) {
    userIds.add(order.getUserId());
}
System.out.println("不重复用户数:" + userIds.size());

3.4 什么时候用 LinkedHashSet?

  • 去重 + 保持顺序:需要去除重复,同时保持插入顺序
  • 实现 LRU 缓存:结合 LinkedHashMap 实现
java 复制代码
// 场景:实现最近访问记录(保持插入顺序)
Set<String> recent = new LinkedHashSet<>();
recent.add("page1");
recent.add("page2");
recent.add("page1");  // 重复,移到末尾
System.out.println(recent);  // [page2, page1]

3.5 什么时候用 TreeSet?

  • 排序:需要按自然顺序或自定义顺序排序
  • 范围查询:需要查找某个范围内的元素
java 复制代码
// 场景:成绩排名(自然排序)
Set<Integer> scores = new TreeSet<>();
scores.add(85);
scores.add(92);
scores.add(78);
scores.add(92);  // 重复,不添加
System.out.println(scores);  // [78, 85, 92]

// 场景:自定义排序
Set<User> users = new TreeSet<>(Comparator.comparing(User::getAge));
users.add(new User("Tom", 25));
users.add(new User("Jerry", 22));
users.add(new User("Bob", 28));
// 按年龄排序

4. Map:键值对 🗝️

4.1 HashMap vs LinkedHashMap vs TreeMap

特性 HashMap LinkedHashMap TreeMap
底层实现 哈希表 哈希表 + 链表 红黑树
顺序 无序 保持插入顺序 排序
查找/添加 O(1) O(1) O(log n)
线程安全
允许 null 1 个 null key + 多个 null value 同左 否(key 不能为 null)
java 复制代码
// HashMap:无序
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("apple", 1);
hashMap.put("banana", 2);
hashMap.put("apple", 3);  // 覆盖
System.out.println(hashMap);  // {banana=2, apple=3}

// LinkedHashMap:保持插入顺序
Map<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("apple", 1);
linkedMap.put("banana", 2);
linkedMap.put("cherry", 3);
System.out.println(linkedMap);  // {apple=1, banana=2, cherry=3}

// TreeMap:排序
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("cherry", 3);
treeMap.put("apple", 1);
treeMap.put("banana", 2);
System.out.println(treeMap);  // {apple=1, banana=2, cherry=3}

4.2 HashMap 底层实现

JDK 8 及之后:数组 + 链表 + 红黑树

java 复制代码
// HashMap 核心结构
public class HashMap<K,V> {
    // 哈希表数组
    transient Node<K,V>[] table;
    
    // 节点结构
    static class Node<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;  // 链表 next
    }
}

put 流程

text 复制代码
put(key, value):
  │
  ▼
1. 计算 key 的 hash
  │
  ▼
2. 计算数组下标(hash & (length-1))
  │
  ▼
3. 数组下标位置为空?
  ├─ 是 → 直接创建节点放入
  └─ 否 → 遍历链表/红黑树
          │
          ▼
4. key 已存在?
  ├─ 是 → 覆盖 value
  └─ 否 → 添加到链表/红黑树
          │
          ▼
5. 链表长度 > 8 且数组长度 > 64?
  └─ 是 → 链表转红黑树

4.3 HashMap 扩容机制

java 复制代码
// 扩容阈值 = 容量 * 加载因子
threshold = capacity * loadFactor  // 默认 0.75

// 扩容时容量翻倍
newCap = oldCap * 2

建议:预估容量,避免频繁扩容:

java 复制代码
// ❌ 频繁扩容
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
    map.put("key" + i, i);
}

// ✅ 预估容量
Map<String, Object> map = new HashMap<>(20000);
for (int i = 0; i < 10000; i++) {
    map.put("key" + i, i);
}

4.4 什么时候用 HashMap?

  • 键值对存储:需要通过 key 快速查找 value
  • 不关心顺序:只要唯一性
  • 高性能:需要 O(1) 的查找/添加
java 复制代码
// 场景:用户缓存
Map<Long, User> userCache = new HashMap<>();
userCache.put(1L, new User(1L, "Tom"));
User user = userCache.get(1L);  // O(1)

4.5 什么时候用 LinkedHashMap?

  • 保持顺序:需要保持插入顺序或访问顺序
  • 实现 LRU 缓存:结合 removeEldestEntry 实现
java 复制代码
// 场景:保持配置项的顺序
Map<String, String> config = new LinkedHashMap<>();
config.put("host", "localhost");
config.put("port", "8080");
config.put("db", "mydb");
// 遍历顺序和插入顺序一致

4.6 什么时候用 TreeMap?

  • 排序:需要按 key 排序
  • 范围查询:需要查找某个范围内的 key
java 复制代码
// 场景:按日期分组
Map<LocalDate, List<Order>> ordersByDate = new TreeMap<>();
for (Order order : orders) {
    LocalDate date = order.getCreateTime().toLocalDate();
    ordersByDate.computeIfAbsent(date, k -> new ArrayList<>()).add(order);
}

// 场景:范围查询
NavigableMap<Integer, String> range = new TreeMap<>();
range.put(1, "a");
range.put(5, "e");
range.put(10, "j");
System.out.println(range.subMap(3, 8));  // {5=e}

5. 线程安全集合 🚦

5.1 线程不安全的集合

ArrayList、HashSet、HashMap 都是线程不安全的,在多线程环境下直接使用会有问题:

java 复制代码
// ❌ 线程不安全
List<Integer> list = new ArrayList<>();

// 多线程同时添加
for (int i = 0; i < 1000; i++) {
    new Thread(() -> list.add(1)).start();
}
// 结果可能小于 1000(并发问题)

5.2 同步集合(早期方案)

java 复制代码
// Collections.synchronizedList
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// Collections.synchronizedSet
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());

// Collections.synchronizedMap
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

原理 :在所有方法上加 synchronized性能差

5.3 ConcurrentHashMap:并发 HashMap

推荐使用,比 Hashtable 性能好:

java 复制代码
// ✅ 推荐:ConcurrentHashMap
Map<String, Object> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("key", "value");
Object val = concurrentMap.get("key");

// ❌ 不推荐:Hashtable(所有方法 synchronized,性能差)
Map<String, Object> hashtable = new Hashtable<>();

ConcurrentHashMap 原理

  • JDK 7:分段锁(Segment),每个 Segment 相当于一个 HashMap
  • JDK 8:CAS + synchronized,锁细化到每个桶

5.4 CopyOnWriteArrayList:读多写少

适合读多写少的场景

java 复制代码
// 读多写少场景
List<String> cowList = new CopyOnWriteArrayList<>();
cowList.add("a");
cowList.add("b");

// 读操作无锁
for (String s : cowList) {
    System.out.println(s);
}

// 写操作:复制整个数组(性能差,不适合写多)
cowList.add("c");  // 复制新数组

5.5 ConcurrentLinkedQueue:无界队列

java 复制代码
// 无界队列
Queue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("a");
queue.offer("b");
String head = queue.poll();  // a

5.6 线程安全集合选型

场景 ���荐 不推荐
高并发 Map ConcurrentHashMap Hashtable, synchronizedMap
高并发 List CopyOnWriteArrayList(读多写少) synchronizedList
高并发 Set CopyOnWriteArraySet(读多写少) synchronizedSet
高并发 Queue ConcurrentLinkedQueue LinkedList(线程不安全)
需要阻塞等待 BlockingQueue 系列 ---

6. Collections 工具类 🔧

6.1 排序

java 复制代码
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));

// 排序
Collections.sort(list);  // [1, 2, 3]

// 逆序
Collections.reverse(list);  // [3, 2, 1]

// 洗牌
Collections.shuffle(list);  // 随机顺序

6.2 查找

java 复制代码
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// 二分查找(必须先排序)
Collections.sort(list);
int index = Collections.binarySearch(list, "b");  // 1

// 查找最大/最小
String max = Collections.max(list);  // "c"
String min = Collections.min(list);  // "a"

6.3 同步包装

java 复制代码
// 同步 List
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// 同步 Set
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());

// 同步 Map
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

// 只读包装
List<String> readOnlyList = Collections.unmodifiableList(new ArrayList<>());

6.4 空集合

java 复制代码
// 空 List(不可变)
List<String> emptyList = Collections.emptyList();

// 单元素 List
List<String> singletonList = Collections.singletonList("only");

// 空 Map
Map<String, Object> emptyMap = Collections.emptyMap();

7. 实战:集合选型决策树 🌲

7.1 List 选型

text 复制代码
需要 List?
  │
  ├─ 需要线程安全?
  │   ├─ 是 → CopyOnWriteArrayList(读多写少)
  │   └─ 否 → 需要随机访问?
  │           ├─ 是 → ArrayList
  │           └─ 否 → 需要 Queue 功能?
  │                   └─ 是 → LinkedList
  │                   └─ 否 → ArrayList

7.2 Set 选型

text 复制代码
需要 Set?
  │
  ├─ 需要线程安全?
  │   ├─ 是 → CopyOnWriteArraySet(读多写少)
  │   └─ 否 → 需要排序?
  │           ├─ 是 → TreeSet
  │           └─ 否 → 需要保持顺序?
  │                   ├─ 是 → LinkedHashSet
  │                   └─ 否 → HashSet

7.3 Map 选型

text 复制代码
需要 Map?
  │
  ├─ 需要线程安全?
  │   ├─ 是 → ConcurrentHashMap
  │   └─ 否 → 需要排序?
  │           ├─ 是 → TreeMap
  │           └─ 否 → 需要保持顺序?
  │                   ├─ 是 → LinkedHashMap
  │                   └─ 否 → HashMap

8. 常见集合面试题 📝

8.1 ArrayList 和 LinkedList 的区别?

  • ArrayList 底层是数组,LinkedList 底层是双向链表
  • ArrayList 随机访问 O(1),LinkedList 随机访问 O(n)
  • ArrayList 头部插入/删除 O(n),LinkedList 头部插入/删除 O(1)
  • ArrayList 内存占用小,LinkedList 需要额外存储指针

8.2 HashMap 的底层实现?

  • JDK 7:数组 + 链表
  • JDK 8:数组 + 链表 + 红黑树(链表长度 > 8 且数组长度 > 64 时转换)

8.3 HashMap 和 Hashtable 的区别?

  • HashMap 线程不安全,Hashtable 线程安全
  • HashMap 允许 null key 和 null value,Hashtable 不允许
  • HashMap 性能更好,推荐使用

8.4 HashSet 怎么保证元素唯一?

  • HashSet 底层是 HashMap,用 key 存储元素,value 存固定值
  • HashMap 的 key 不能重复,所以 HashSet 元素唯一

8.5 ConcurrentHashMap 为什么高效?

  • JDK 7:分段锁,每个 Segment 独立加锁
  • JDK 8:CAS + synchronized,锁细化到每个桶,减少锁竞争

9. 集合的常见操作与性能优化 ⚡

9.1 遍历方式选择

java 复制代码
List<String> list = Arrays.asList("a", "b", "c");

// 方式 1:for 循环(推荐 for ArrayList)
for (int i = 0; i < list.size(); i++) {
    String s = list.get(i);
}

// 方式 2:增强 for 循环
for (String s : list) {
    // ...
}

// 方式 3:Iterator(推荐 for LinkedList,安全删除)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if ("b".equals(s)) {
        iterator.remove();  // 安全删除
    }
}

// 方式 4:forEach(Lambda)
list.forEach(s -> System.out.println(s));

// 方式 5:Stream(Java 8+)
list.stream()
    .filter(s -> s.startsWith("a"))
    .forEach(System.out::println);

9.2 集合转数组

java 复制代码
List<String> list = Arrays.asList("a", "b", "c");

// 方式 1:toArray
String[] array = list.toArray(new String[0]);

// 方式 2:toArray(指定大小)
String[] array2 = list.toArray(new String[list.size()]);

9.3 数组转集合

java 复制代码
String[] array = {"a", "b", "c"};

// 方式 1:Arrays.asList(返回固定大小列表)
List<String> list = Arrays.asList(array);

// 方式 2:ArrayList 构造方法(可修改)
List<String> list2 = new ArrayList<>(Arrays.asList(array));

// 方式 3:List.of(Java 9+,不可变)
List<String> list3 = List.of(array);

9.4 集合去重

java 复制代码
List<Integer> list = Arrays.asList(1, 2, 3, 2, 1);

// 方式 1:HashSet(无序)
List<Integer> distinct1 = new ArrayList<>(new HashSet<>(list));

// 方式 2:Stream(Java 8+)
List<Integer> distinct2 = list.stream().distinct().collect(Collectors.toList());

// 方式 3:LinkedHashSet(保持顺序)
List<Integer> distinct3 = new ArrayList<>(new LinkedHashSet<>(list));

9.5 集合交集/并集/差集

java 复制代码
List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
List<Integer> list2 = Arrays.asList(3, 4, 5, 6);

// 交集
List<Integer> intersection = list1.stream()
    .filter(list2::contains)
    .collect(Collectors.toList());

// 并集
Set<Integer> union = new HashSet<>(list1);
union.addAll(list2);

// 差集(list1 - list2)
List<Integer> difference = list1.stream()
    .filter(item -> !list2.contains(item))
    .collect(Collectors.toList());

9.6 性能优化建议

java 复制代码
// ❌ 频繁扩容
List<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    list.add("item" + i);
}

// ✅ 预估容量
List<String> list = new ArrayList<>(150000);
for (int i = 0; i < 100000; i++) {
    list.add("item" + i);
}

// ❌ 在 List 头部频繁插入
List<String> list = new ArrayList<>();
list.add(0, "new");  // O(n)

// ✅ 使用 LinkedList 在头部插入
LinkedList<String> linkedList = new LinkedList<>();
linkedList.addFirst("new");  // O(1)

// ❌ 循环中拼接字符串
String result = "";
for (String s : list) {
    result += s + ",";
}

// ✅ 使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : list) {
    sb.append(s).append(",");
}
String result = sb.toString();

10. Spring Boot 中的集合使用 📦

10.1 配置属性绑定集合

java 复制代码
// 配置类
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    private List<String> allowedOrigins;
    private Map<String, String> settings;
    // getters/setters
}
yaml 复制代码
# application.yml
app:
  allowed-origins:
    - http://localhost:3000
    - http://localhost:8080
  settings:
    timeout: 30
    max-retries: 3

10.2 REST 接口返回集合

java 复制代码
@GetMapping("/users")
public List<User> getUsers() {
    return userService.list();
}

@PostMapping("/users/batch")
public Result<Void> batchAdd(@RequestBody List<User> users) {
    userService.saveBatch(users);
    return Result.success();
}

10.3 缓存集合数据

java 复制代码
@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#root.methodName")
    public List<User> getAllUsers() {
        return userMapper.selectList(null);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void refreshUsers() {
        // 清除缓存
    }
}

11. 集合与泛型 🔧

11.1 泛型集合

java 复制代码
// 泛型 List
List<String> stringList = new ArrayList<>();
stringList.add("hello");
String s = stringList.get(0);

// 泛型 Map
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
Integer value = map.get("one");

11.2 泛型方法

java 复制代码
// 泛型方法
public <T> T getFirst(List<T> list) {
    return list.isEmpty() ? null : list.get(0);
}

// 使用
String first = getFirst(Arrays.asList("a", "b", "c"));
Integer num = getFirst(Arrays.asList(1, 2, 3));

11.3 泛型通配符

java 复制代码
// ? extends T:读取 T 的子类型
void read(List<? extends Number> list) {
    Number num = list.get(0);  // 可以读
    // list.add(1);  // 不能写
}

// ? super T:写入 T 的父类型
void write(List<? super Integer> list) {
    list.add(1);  // 可以写
    Object obj = list.get(0);  // 只能读 Object
}

// PECS 原则
// Producer(生产者)用 extends
// Consumer(消费者)用 super

12. 集合常见错误与避免 🚨

12.1 ConcurrentModificationException

java 复制代码
// ❌ 错误:遍历时修改集合
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
    if ("a".equals(s)) {
        list.remove(s);  // ConcurrentModificationException
    }
}

// ✅ 正确:使用 Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if ("a".equals(s)) {
        iterator.remove();
    }
}

// ✅ 正确:使用 removeIf(Java 8+)
list.removeIf("a"::equals);

// ✅ 正确:使用 CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : cowList) {
    if ("a".equals(s)) {
        cowList.remove(s);  // 安全
    }
}

12.2 NullPointerException

java 复制代码
// ❌ 错误:集合为 null
List<String> list = null;
list.add("a");  // NPE

// ✅ 正确:初始化集合
List<String> list = new ArrayList<>();
list.add("a");

// ✅ 正确:空集合检查
if (list != null && !list.isEmpty()) {
    // 处理
}

// ✅ 正确:使用 Optional
Optional.ofNullable(list)
    .ifPresent(l -> l.add("a"));

12.3 集合元素为 null

java 复制代码
// ❌ 错误:集合中包含 null
List<String> list = new ArrayList<>();
list.add(null);
list.add("a");
for (String s : list) {
    System.out.println(s.length());  // NPE
}

// ✅ 正确:过滤 null
list.removeIf(Objects::isNull);

// ✅ 正确:处理 null
for (String s : list) {
    if (s != null) {
        System.out.println(s.length());
    }
}

12.4 线程安全问题

java 复制代码
// ❌ 错误:多线程共享非线程安全集合
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < 1000; i++) {
    final int j = i;
    new Thread(() -> map.put("key" + j, j)).start();
}

// ✅ 正确:使用 ConcurrentHashMap
Map<String, Object> concurrentMap = new ConcurrentHashMap<>();
for (int i = 0; i < 1000; i++) {
    final int j = i;
    new Thread(() -> concurrentMap.put("key" + j, j)).start();
}

小结

  • List:ArrayList(随机访问)、LinkedList(头部操作/Queue)、Vector(已淘汰)
  • Set:HashSet(去重)、LinkedHashSet(去重+顺序)、TreeSet(去重+排序)
  • Map:HashMap(键值对)、LinkedHashMap(顺序)、TreeMap(排序)
  • 线程安全:用 ConcurrentHashMap、CopyOnWriteArrayList 等并发集合
  • Collections 工具类:排序、查找、同步包装、空集合
  • 选型原则:根据是否需要顺序、是否需要排序、是否需要线程安全来选择

下一篇(017)预告:泛型与通配符:API 设计里怎么用省心------泛型类/方法/接口、上下界通配符、 PECS 原则。

相关推荐
Predestination王瀞潞1 小时前
Java EE3-我独自整合(第六章:Spring AOP 工作流程与切入点表达式)
java·spring·java-ee
景庆1971 小时前
vscode启动springBoot项目配置,激活环境
java·开发语言·vscode
小则又沐风a1 小时前
Linux使用指南和基础指令(1)
java·linux·运维
自我意识的多元宇宙2 小时前
二叉树的遍历和线索二叉树--先序二叉树和后续二叉树
数据结构
im_AMBER2 小时前
Leetcode 159 无重复字符的最长子串 | 长度最小的子数组
javascript·数据结构·学习·算法·leetcode
三千星2 小时前
Java开发者转型AI工程化Week 2:从核心能力到生产就绪
java·ai编程
亦暖筑序2 小时前
让 AI 客服真能用的 3 个模块:情绪感知 + 意图识别 + Agent 工具链
java·人工智能·后端
郝学胜-神的一滴2 小时前
[力扣 105]二叉树前中后序遍历精讲:原理、实现与二叉树还原
数据结构·c++·算法·leetcode·职场和发展
SimonKing2 小时前
别让你的代码裸奔!Spring Boot混淆全攻略(附配置)
java·后端·程序员