前言

很多同学面试时对 HashMap 的红黑树对答如流,但在写代码时却经常写出 Arrays.asList() 导致的崩溃,或者在循环中删除元素导致的异常。本篇将从实战角度出发,梳理 Java 集合在使用过程中的六大核心注意事项


一、 集合判空:不要只依赖 null 检查

在业务逻辑中,判断集合是否有元素,推荐使用 isEmpty() 而不是 size() == 0,更不能只靠 != null

  • 规范建议

    1. 使用 Collection.isEmpty(),语义更清晰,性能在某些特定集合(如 ConcurrentLinkedQueue)下优于 size()

    2. 推荐使用工具类:CollectionUtils.isEmpty(collection)(Apache Commons 或 Spring 提供),它能同时帮你做非空和长度为 0 的判断。

Java

// ✅ 推荐做法
if (CollectionUtils.isNotEmpty(list)) {
    // 执行业务逻辑
}

二、 集合转数组:必须使用 toArray(T[] array)

当我们需要把一个 List 转换成数组时,千万不要直接使用无参的 toArray()

  • 避坑点:无参 toArray() 返回的是 Object[] 数组,强转成其他类型会抛出 ClassCastException

  • 最佳实践: 使用 toArray(T[] array),并传入一个长度为 0 的数组。

Java

List<String> list = new ArrayList<>(Arrays.asList("Java", "Go"));
// ✅ 推荐做法:使用长度为 0 的数组
String[] array = list.toArray(new String[0]); 

原理分析:传入 new String[0] 既能保证类型安全,又避免了预先计算长度的开销,因为 JVM 内部会对这种写法进行优化,比预设长度性能更好。


三、 数组转集合:谨防 Arrays.asList() 的陷阱

这是新手最容易掉进去的坑。

1. 它是“伪 List”

Arrays.asList() 返回的是 Arrays 内部的一个静态内部类,它并没有实现 add()remove() 方法。

  • 结果:如果你对它执行 addremove,会直接抛出 UnsupportedOperationException

2. 它是“联动的”

这个 List 底层直接引用了原数组。如果你修改了原数组的值,List 里的值也会跟着变。

3. 基础类型不支持

如果你传入一个 int[],它不会转成 List<Integer>,而是转成了一个 List<int[]>,这通常不是你想要的结果。

✅ 正确姿势:

Java

// 如果需要对转换后的 List 进行增删
List<String> list = new ArrayList<>(Arrays.asList("A", "B")); 
// Java 9+ 推荐
List<String> list2 = List.of("A", "B"); 

四、 集合遍历:禁止在 for-each 中增删元素

这是一个经典的面试题,也是高频的线上 Bug。

  • 错误现象:在 for 循环或 for-each 中调用 list.remove(),会抛出 ConcurrentModificationException (CME)。

  • 原理解析:Java 的集合在迭代时维护了一个 modCount(修改计数器),如果你在迭代过程中通过集合自带的方法修改了长度,计数器就会对不上,触发 fail-fast 机制。

✅ 正确姿势:

  1. 使用 Iteratorremove() 方法。

  2. Java 8+ 使用 removeIf() 方法。

Java

// ✅ 推荐做法 (Java 8+)
list.removeIf(filter -> filter.contains("target"));

五、 集合去重:不仅仅是 distinct()

很多人喜欢用 list.stream().distinct() 来去重,虽然方便,但如果数据量巨大,性能远不如 HashSet

  • 性能对比

    • HashSet 利用哈希表,查找重复元素的效率接近 O(1)。

    • 如果对一个大列表去重,先转成 HashSet 再转回 List 往往是最高效的。

Java

// 如果不要求顺序
Set<String> set = new HashSet<>(largeList);
List<String> result = new ArrayList<>(set);

六、 集合排序:不可忽视的稳定性

使用 Collections.sort()list.sort() 时,底层使用的是 TimSort(一种混合排序算法)。

  • 注意事项

    1. 稳定性:Java 的集合排序是稳定的(相同元素的相对位置不会变)。

    2. 比较器规范:实现 Comparator 时,必须满足 自反性、传递性、对称性。如果逻辑不严谨(例如在大规模数据下 a > b 返回 1,但忘记处理 a == b 返回 0 的情况),可能会在运行时抛出 Comparison method violates its general contract! 异常。


七、 总结:资深开发的代码守则

  1. 明确场景:读多写少用 ArrayList,去重用 HashSet,并发用 ConcurrentHashMap

  2. 防御式编程:返回集合方法时,如果没数据,返回 Collections.emptyList() 而不是 null,可以有效减少调用方的空指针风险。

  3. 性能意识:大集合初始化记得指定 initialCapacity

  4. 安全意识:不可变场景下,使用 Collections.unmodifiableList() 包装,防止集合被下游业务意外修改。


结语: Java 集合框架就像一套精密的工具箱,用好了事半功倍,用错了处处是坑。希望这三篇集合系列文章能帮你夯实基础,在面试和实战中游刃有余!

喜欢这篇文章吗?欢迎在评论区留言讨论!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐