集合使用注意事项
集合使用注意事项
集合使用中的常见陷阱和解决方案,避免生产环境中的 bug。
集合转 Map
toMap() 的 value 不能为 null
Collectors.toMap() 内部调用 Map.merge(),merge 会用 Objects.requireNonNull(value) 检查,导致 NPE。
// 抛出 NPE!phoneNumber 为 null 时
list.stream().collect(Collectors.toMap(Person::getName, Person::getPhoneNumber));注意
解决方案 使用三参数重载:Collectors.toMap(keyMapper, valueMapper, (v1, v2) -> v1)
数组转集合
Arrays.asList() 陷阱
陷阱1:基本类型数组不会自动装箱
int[] arr = {1, 2, 3} 传入 Arrays.asList(arr) 返回 size=1 的列表,元素是数组地址。必须使用 Integer[]。
陷阱2:返回的 List 不支持 add/remove/clear
Arrays.asList() 返回 Arrays$ArrayList(内部视图类),调用 add()/remove() 抛 UnsupportedOperationException。
正确转换方式:
Integer[] arr = {1, 2, 3};
List<Integer> list1 = new ArrayList<>(Arrays.asList(arr)); // ArrayList 构造
List<Integer> list2 = Arrays.stream(arr).boxed().collect(Collectors.toList()); // Stream(推荐)
List<Integer> list3 = List.of(arr); // JDK9+
List<Integer> list4 = ImmutableList.copyOf(arr); // Guava强制规则
| 规则 | 说明 |
|---|---|
| 覆写 equals 必须覆写 hashCode | 否则 HashMap/HashSet 行为异常 |
| 集合不允许存放自身 | 递归引用导致栈溢出 |
| subList 结果不可强转 ArrayList | subList 返回内部类 SubList |
| toArray 传入长度 0 的数组 | toArray(new T[0]) 性能更优 |
| ConcurrentHashMap 禁用 null | 无法区分"键不存在"和"值为 null" |
ConcurrentHashMap 复合操作
ConcurrentHashMap 保证单个操作的线程安全,但不保证复合操作的原子性。
// 竞态条件:两个线程同时判断 key 不存在,都执行 put
if (!map.containsKey(key)) {
map.put(key, value); // 线程 B 的值可能被覆盖
}使用原子性复合方法
putIfAbsent、compute、computeIfAbsent、computeIfPresent、merge 均为原子操作。
map.putIfAbsent(key, value); // 原子插入
map.computeIfAbsent(key, k -> calculateValue()); // 原子计算并插入