别再自己写排序了!从二分查找到同步包装,从空集合到不可修改视图,这篇带你榨干 Collections 的所有价值
java.util.Collections是 Java 集合框架的“瑞士军刀”——一个由静态方法组成的工具类,专门用来操作Collection及其子类。
它提供了:
- 排序与搜索(
sort、binarySearch) - 线程安全包装(
synchronizedXxx) - 不可修改视图(
unmodifiableXxx) - 空集合与单例集合(
emptyList、singletonList) - 批量操作(
addAll、fill、reverse)
今天这篇文章,我们把Collections工具类的核心方法、底层原理、经典陷阱和最佳实践一网打尽。
一、先上全家福(一张表看懂常用方法)
| 方法分类 | 方法名 | 作用 |
|---|---|---|
| 排序与混排 | sort(List<T> list) | 升序排序(必须实现Comparable) |
sort(List<T> list, Comparator<T> c) | 按自定义比较器排序 | |
reverse(List<?> list) | 反转列表顺序 | |
shuffle(List<?> list) | 随机打乱列表 | |
swap(List<?> list, int i, int j) | 交换指定位置的两个元素 | |
| 查找与极值 | binarySearch(List<?> list, T key) | 二分查找(必须已排序) |
max(Collection<?> coll) | 返回最大元素 | |
min(Collection<?> coll) | 返回最小元素 | |
frequency(Collection<?> c, Object o) | 统计元素出现次数 | |
disjoint(Collection<?> c1, Collection<?> c2) | 判断两个集合是否无交集 | |
| 不可修改视图 | unmodifiableList(List<?> list) | 返回只读 List(视图) |
unmodifiableSet(Set<?> set) | 返回只读 Set | |
unmodifiableMap(Map<?,?> m) | 返回只读 Map | |
| 线程安全包装 | synchronizedList(List<T> list) | 返回线程安全的 List(包装) |
synchronizedSet(Set<T> s) | 返回线程安全的 Set | |
synchronizedMap(Map<K,V> m) | 返回线程安全的 Map | |
| 空集合与单例 | emptyList() | 返回空 List(类型安全) |
singletonList(T o) | 返回只含 1 个元素的 List | |
singleton(T o) | 返回只含 1 个元素的 Set | |
| 批量操作 | addAll(Collection<? super T> c, T... elements) | 批量添加元素 |
fill(List<? super T> list, T obj) | 用同一个元素填充整个列表 | |
copy(List<? super T> dest, List<? extends T> src) | 复制列表(目标长度需 ≥ 源长度) | |
replaceAll(List<T> list, T oldVal, T newVal) | 替换所有匹配元素 |
二、排序、混排与极值(最常用的日常操作)
1. 排序(sort)
List<Integer>numbers=newArrayList<>(Arrays.asList(5,2,8,1,9));Collections.sort(numbers);System.out.println(numbers);// [1, 2, 5, 8, 9]// 自定义比较器(降序)Collections.sort(numbers,(a,b)->b-a);System.out.println(numbers);// [9, 8, 5, 2, 1]// 对象排序(实现 Comparable)classPersonimplementsComparable<Person>{Stringname;intage;// getters/setters...@OverridepublicintcompareTo(Persono){returnthis.age-o.age;// 按年龄升序}}List<Person>people=...;Collections.sort(people);源码简析:
Collections.sort()底层调用的是List.sort(),而List.sort()在 Java 8+ 中调用的是TimSort(一种优化的归并排序,对部分有序数据表现极佳)。TimSort是Arrays.sort()对对象数组的实现,稳定性高,时间复杂度 O(n log n)。
2. 乱序(shuffle)
List<String>cards=Arrays.asList("A","2","3","4","5");Collections.shuffle(cards);// 随机打乱(默认使用 Random)Collections.shuffle(cards,newRandom(42));// 指定随机种子(可复现)3. 反转与交换
List<String>list=Arrays.asList("A","B","C");Collections.reverse(list);// [C, B, A]Collections.swap(list,0,1);// 交换索引 0 和 1 → [B, C, A]4. 最大/最小元素(max/min)
List<Integer>nums=Arrays.asList(3,7,2,9,5);System.out.println(Collections.max(nums));// 9System.out.println(Collections.min(nums));// 2// 按字符串长度取最大List<String>words=Arrays.asList("cat","elephant","dog");Stringlongest=Collections.max(words,Comparator.comparing(String::length));System.out.println(longest);// "elephant"三、二分查找(binarySearch)—— 前提必须是已排序
List<Integer>list=newArrayList<>(Arrays.asList(1,3,5,7,9));Collections.sort(list);// 必须排序!intindex=Collections.binarySearch(list,5);System.out.println(index);// 2intnotFound=Collections.binarySearch(list,4);System.out.println(notFound);// -3(插入点:-(应插入位置)- 1)⚠️警告:如果列表未排序,
binarySearch的结果是不可预测的(可能找到错误的位置或直接返回错误索引)。
四、不可修改视图(unmodifiableXxx)—— 只读,但有陷阱!
1. 基本用法
List<String>original=newArrayList<>();original.add("A");original.add("B");List<String>readonly=Collections.unmodifiableList(original);System.out.println(readonly.get(0));// "A"(可以读)// readonly.add("C"); // ❌ 抛出 UnsupportedOperationException!2. ⚠️ 致命陷阱:不可修改视图 ≠ 不可变对象
unmodifiableXxx只是原集合的视图。它挡住了“通过视图”修改的路径,但原集合如果变了,视图也跟着变!
List<String>original=newArrayList<>();original.add("A");List<String>readonly=Collections.unmodifiableList(original);System.out.println(readonly);// [A]// 原集合被修改了!original.add("B");// 视图也跟着变了!System.out.println(readonly);// [A, B] ← 明明是不可修改视图,内容却变了!📌真正的不可变集合(Java 9+):
List.of("A", "B")、Set.of("A"),它们返回的集合是完全不可变的(内容和引用都不可变)。
3. 正确用法
- 如果想把集合快照保护起来,应该在包装后丢弃原集合引用,或干脆使用
List.copyOf(original)(Java 10+),它返回的是不可变副本。
五、线程安全包装(synchronizedXxx)—— 大坑预警!
1. 基本用法
List<String>unsafeList=newArrayList<>();List<String>safeList=Collections.synchronizedList(unsafeList);// 多线程环境下,操作 safeList 是安全的2. ⚠️ 致命陷阱:迭代器仍然需要手动同步!
synchronizedList保证add、remove、get等单个操作是原子的,但迭代器(iterator())不是线程安全的!
List<String>syncList=Collections.synchronizedList(newArrayList<>());// ❌ 错误!遍历时没有加锁,会抛出 ConcurrentModificationExceptionfor(Strings:syncList){System.out.println(s);}// ✅ 正确:手动加锁synchronized(syncList){for(Strings:syncList){System.out.println(s);}}3. 什么时候用?
- 对于
List、Set:Collections.synchronizedXxx可以使用,但要注意手动锁迭代。 - 对于
Map:强烈不推荐Collections.synchronizedMap,直接使用ConcurrentHashMap更好(性能更高,且迭代器弱一致,不会抛ConcurrentModificationException)。
六、空集合与单例集合(告别null)
1. 空集合(emptyXxx)—— 代替null的最佳实践
// ❌ 错误:返回 null,调用方必须判空publicList<String>getNames(){returnnull;}// ✅ 正确:返回空集合,调用方可以直接遍历(for 循环不报错)publicList<String>getNames(){returnCollections.emptyList();// 每次返回同一个对象,不分配新内存}// 使用示例List<String>names=getNames();for(Stringname:names){// 安全,不会 NPESystem.out.println(name);}📌类型安全:
Collections.emptyList()返回的是List<T>(类型推导),Collections.emptyList()和Collections.<String>emptyList()都是合法的。
2. 单例集合(singletonXxx)—— 极简元素集合
Set<String>singletonSet=Collections.singleton("onlyOne");List<Integer>singletonList=Collections.singletonList(42);// 场景:防止批量操作误伤,只传入一个元素List<Person>blacklist=Collections.singletonList(newPerson("BadGuy"));📌性能优势:
singletonXxx返回的集合是极度轻量级的(不分配数组空间,只有一个引用),比Arrays.asList()或new ArrayList<>()内存效率高得多。
七、批量操作与频率统计
1. 批量添加(addAll)
List<String>list=newArrayList<>();Collections.addAll(list,"A","B","C","D");// 比逐个 add 更方便Collections.addAll(list,newString[]{"E","F"});// 也可以加数组2. 填充(fill)
List<String>list=newArrayList<>(Arrays.asList("A","B","C"));Collections.fill(list,"Z");System.out.println(list);// [Z, Z, Z]3. 复制(copy)—— 注意目标长度!
List<String>src=Arrays.asList("A","B","C");List<String>dest=newArrayList<>(Arrays.asList("X","X","X","X"));Collections.copy(dest,src);// dest 必须有 >= src 的长度System.out.println(dest);// [A, B, C, X](只覆盖前 3 个)⚠️ 如果
dest.size() < src.size(),会抛出IndexOutOfBoundsException。
4. 频率统计与交集判断
List<String>list=Arrays.asList("A","B","A","C","A");System.out.println(Collections.frequency(list,"A"));// 3List<String>list1=Arrays.asList("A","B","C");List<String>list2=Arrays.asList("C","D","E");System.out.println(Collections.disjoint(list1,list2));// false(有交集 C)List<String>list3=Arrays.asList("X","Y");System.out.println(Collections.disjoint(list1,list3));// true(无交集)八、面试高频陷阱与反直觉结论汇总
陷阱 1:Arrays.asList()返回的 List 不支持add/remove
List<String>list=Arrays.asList("A","B");list.add("C");// ❌ UnsupportedOperationException!原因:Arrays.asList()返回的是Arrays.ArrayList(一个固定长度的内部类),不是java.util.ArrayList。它不支持结构性修改(add/remove),但支持set修改元素。
陷阱 2:Collections.unmodifiableList不是“快照”
前面已讲,原集合修改会影响视图。
陷阱 3:Collections.synchronizedList的迭代需要外部锁
前面已讲,不手动synchronized会抛异常。
陷阱 4:Collections.sort要求List必须可变且支持set
Arrays.asList()的 List 支持set,所以可以排序。但通过List.of()(Java 9+)创建的是完全不可变 List,不能排序(会抛异常)。
九、Collections与Arrays工具类的区别
| 对比 | Collections | Arrays |
|---|---|---|
| 操作对象 | 集合类(List、Set、Map) | 数组(int[]、String[]) |
| 常用方法 | sort、reverse、binarySearch、synchronizedXxx | sort、binarySearch、asList、copyOf、toString |
| 重点关注 | 线程安全包装、不可修改视图、空集合 | 数组与集合互转、数组复制 |
十、思考题(检验是否真的懂了)
// 问题 1:下面代码输出什么?为什么?List<String>original=newArrayList<>(Arrays.asList("A","B"));List<String>readonly=Collections.unmodifiableList(original);original.add("C");System.out.println(readonly.size());// 问题 2:下面代码在多线程环境下安全吗?为什么?List<Integer>list=Collections.synchronizedList(newArrayList<>());// 线程 Afor(Integeri:list){System.out.println(i);}// 线程 Blist.add(999);// 问题 3:下列哪些是返回不可变集合的正确方式(Java 8 和 Java 11)?// A. Collections.unmodifiableList(new ArrayList<>(...))// B. Collections.emptyList()// C. List.of("A", "B")// D. Arrays.asList("A", "B")答案(选中下方空白区域查看):
- 输出 3。
unmodifiableList只是原集合的视图,原集合添加元素后,视图的大小也会随之改变。- 不安全。虽然
synchronizedList保证单个操作安全,但遍历没有加外部同步锁。线程 A 在遍历时,线程 B 添加元素,会抛出ConcurrentModificationException。必须在外部synchronized(list) { ... }包裹遍历代码。- A、B、C 都返回不可变集合,但机制不同:
- A:视图不可变,但原集合若被修改,视图会变(不安全)。
- B:空集合,完全不可变。
- C:
List.of()返回的集合完全不可变(内容和大小都无法改变)。- D:
Arrays.asList()返回的集合长度固定,但支持set修改元素值,因此它不是完全不可变的。
总结(终极速查表)
| 需求 | 推荐方法 | 注意事项 |
|---|---|---|
| 排序 | Collections.sort(list) | 需要List可变,且元素实现Comparable或提供Comparator |
| 随机打乱 | Collections.shuffle(list) | 可指定Random种子 |
| 二分查找 | Collections.binarySearch(list, key) | 必须已排序,否则结果错误 |
| 返回空集合 | Collections.emptyList() | 优于返回null,类型安全且无内存开销 |
| 返回单元素集合 | Collections.singletonList(item) | 内存效率极高 |
| 只读视图 | Collections.unmodifiableList(list) | 原集合变化会反映到视图,注意! |
| 真正的不可变集合 | List.of(...)(Java 9+)或List.copyOf(...)(Java 10+) | 完全不可变,推荐新项目使用 |
| 线程安全的 List | Collections.synchronizedList(list) | 遍历时必须手动synchronized |
| 线程安全的 Map | ConcurrentHashMap(不用Collections.synchronizedMap) | 性能更好,迭代器弱一致性 |
| 批量添加 | Collections.addAll(list, elements) | 比循环add更高效 |
💬 互动话题:你有没有在线上环境因为Arrays.asList()返回的 List 调用了add()导致UnsupportedOperationException?或者因为synchronizedList迭代没加锁导致诡异异常?欢迎评论区分享!🚀
如果觉得有收获,别忘了点赞、收藏、转发,让更多 Javaer 彻底搞懂Collections工具类!我们下篇见!👋
发布日期:2026-08-29