写业务代码时,你可能天天都在用 List、Map、Set,但有没有想过: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 原则。