Collections不可变包装仅提供不可修改视图,不复制原集合;真正隔离需先复制再包装;JDK10+推荐List.of()等真正不可变实例;排序查找要求元素可比较,否则运行时抛ClassCastException。

用 Collections 做不可变集合包装,但注意它只是“不可修改视图”
调用 Collections.unmodifiableList()、unmodifiableSet() 等方法,并不会拷贝原集合,而是返回一个代理对象。一旦底层集合被修改,不可变视图在下次访问时仍会抛出 UnsupportedOperationException——但这个异常不是实时拦截的,比如你先获取了迭代器再改底层,就可能触发 ConcurrentModificationException。
常见错误是以为“包装完就安全了”,结果上游还在往原 ArrayList 里 add(),下游读取时突然失败。
- 真正需要隔离修改,请用
new ArrayList(original)先复制再包装 -
unmodifiableMap()对entrySet()、keySet()、values()返回的子视图也做同样封装,但这些子视图仍依赖原Map - JDK 10+ 推荐优先用
List.of()、Set.copyOf(),它们创建的是紧凑、不可变、无 null 的真正不可变实例
排序和二分查找必须保证元素可比较,否则运行时报 ClassCastException
Collections.sort() 和 Collections.binarySearch() 要求集合元素实现 Comparable,或显式传入 Comparator。没实现 Comparable 又没给 Comparator,就会在运行时抛出 ClassCastException,而不是编译错误。
典型场景:对自定义类 User 的 ArrayList 直接调用 sort(),但忘了让 User 实现 Comparable 或传 Comparator.comparing(User::getName)。
立即学习“Java免费学习笔记(深入)”;
- 数组排序用
Arrays.sort(),行为类似,但泛型擦除后对原始类型数组更直接 -
binarySearch()前必须确保集合已升序排列,否则结果无意义 - 对
LinkedList调用sort()性能较差(O(n²) 链表遍历),建议先转ArrayList
线程安全包装器只是同步方法,不能解决复合操作竞态
Collections.synchronizedList() 这类方法返回的对象,只是把每个 public 方法加了 synchronized(this)。单个操作如 get()、size() 是线程安全的,但像“检查是否存在再添加”这种两步操作,依然会出问题。
例如:if (!syncList.contains(x)) syncList.add(x); —— 中间可能被其他线程插入相同元素。
- 这类包装器锁的是集合对象本身,如果多个线程还通过其他引用访问原集合(比如保留了未包装前的引用),就完全失效
- 高并发下性能差,推荐用
CopyOnWriteArrayList(读多写少)、ConcurrentHashMap替代 -
synchronizedMap()的keySet()、entrySet()返回的视图不自动同步,遍历时需手动同步外部锁
其他高频工具方法:空校验、填充、打乱,但要注意副作用和边界
Collections.emptyList()、emptySet()、emptyMap() 返回的是共享的静态单例,轻量且安全;但 fill()、swap()、reverse() 都是就地修改,没有返回新集合。
容易忽略的点:
-
fill(list, obj)会把list所有位置设为同一引用,若obj是可变对象,后续修改会影响所有元素 -
shuffle()默认用Random,在多线程中若共用同一Random实例,可能因内部状态竞争导致重复序列 -
frequency()和disjoint()是只读操作,但传入null集合会直接抛NullPointerException,需提前判空
真正复杂的集合逻辑(比如深层嵌套合并、条件分组、流式转换),Collections 类覆盖不到,得靠 Stream 或第三方库如 Guava。它本质是一组“小而确定”的辅助操作,不是集合功能的替代品。










