Day26~29的文章我们一起学习了Java集合框架的三大核心接口:List、Set和Map。
今天我们一起看一下java.util.Collections类,注意不是之前我们提到的Collection。
Collection接口定义了集合的是什么,多了个s的Collections是一套集合操作的工具箱。
Collections里提供了很多强大、方便的静态方法,能极大简化我们对集合的操作。
一、集合排序
在实际开发中,对数据进行排序是最常见的需求之一。Collections.sort() 方法可以轻松地对任何List集合进行排序。
1.1自然排序
如果List里的元素实现了Comparable接口(如Integer, String 等),sort方法会按照它们的自然顺序进行排序。


java
package com.lazy.snail.day30;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName Day30Demo
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 15:13
* @Version 1.0
*/
public class Day30Demo {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(1);
numbers.add(9);
numbers.add(3);
System.out.println("排序前: " + numbers);
Collections.sort(numbers);
System.out.println("排序后: " + numbers);
}
}
案例中使用了Collections的sort方法,使用对象的自然排序对list进行排序。
1.2自定义排序
如果我们要对自定义对象(如Student)进行排序,sort方法还有一个接受Comparator(比较器)的版本,让我们可以随心所欲地定义排序规则。
java
package com.lazy.snail.day30;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @ClassName Day30Demo2
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 15:21
* @Version 1.0
*/
public class Day30Demo2 {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("懒惰蜗牛", 28));
students.add(new Student("lazysnail", 29));
// 匿名内部类写法
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
});
// Lambda表达式
Collections.sort(students, (o1, o2) -> o1.age - o2.age);
// Comparator工具方法
Collections.sort(students, Comparator.comparingInt(o -> o.age));
System.out.println("按年龄排序后: " + students);
}
}
class Student {
String name;
int age;
public Student(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return name + ":" + age; }
}
上面的案例中Comparator的创建使用了三种写法:
Java8之前传统写法,使用匿名内部类实现Comparator接口。
Java8+的Lambda表达式,这个其实是匿名内部类的语法糖。
Comparator接口是一个函数式接口(只有一个抽象方法),所以可以用Lambda替代。
Comparator.comparingInt()是Comparator提供的静态工厂方法,专门用来对int类型字段的比较。
内部封装了compare的逻辑。
其实自Java8之后,List接口自身提供了一个sort()方法。可以直接在列表对象上调用方法。
ini
students.sort((o1, o2) -> o1.age - o2.age);

在Day28 | Java集合框架之Set接口详解中的TreeSet讲解中讲到了自然排序和自定义排序,可以回顾下。
二、查找
对于一个已经排好序的List,binarySearch方法使用二分查找算法,效率要高于遍历。
使用binarySearch之前,列表必须是有序的!否则,结果就不是你想要的。
效率
java
package com.lazy.snail.day30;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName Day30Demo3
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 15:52
* @Version 1.0
*/
public class Day30Demo3 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
list.add(i);
}
int target = 987654;
// 遍历
long start1 = System.nanoTime();
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == target) {
break;
}
}
long end1 = System.nanoTime();
System.out.println("遍历查找耗时: " + (end1 - start1) / 1_000_000.0 + " ms");
// binarySearch
long start2 = System.nanoTime();
Collections.binarySearch(list, target);
long end2 = System.nanoTime();
System.out.println("binarySearch查找耗时: " + (end2 - start2) / 1_000_000.0 + " ms");
}
}

从运行的结果看,二分查找的效率比普通遍历的效率高得多。
二分查找这种算法,每次都能排除掉一半的数据。相较于普通遍历的线性搜素。
在数据量很多的情况下,二分查找的时间复杂度要远远低于普通遍历。
未排序使用binarySearch
如果对没有排序的列表进行二分查找,结果就是找不到数据。
java
package com.lazy.snail.day30;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @ClassName Day30Demo4
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 16:08
* @Version 1.0
*/
public class Day30Demo4 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(50, 20, 40, 10, 30);
System.out.println("列表内容: " + list);
// 想查找元素 30
int index = Collections.binarySearch(list, 30);
System.out.println("binarySearch 查找结果索引: " + index);
}
}

三、线程安全转换
我们经常用的ArrayList、HashMap等都是非线程安全的。
在多线程环境中并发修改,可能会导致数据错乱。
Collections提供了一系列synchronizedXXX() 方法,可以把它们包装成线程安全的集合。

java
package com.lazy.snail.day30;
import java.util.*;
/**
* @ClassName Day30Demo5
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 16:26
* @Version 1.0
*/
public class Day30Demo5 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
List<String> safeList = Collections.synchronizedList(list);
safeList.add("懒惰蜗牛");
//Set<T> safeSet = Collections.synchronizedSet(new HashSet<>());
//Map<K, V> safeMap = Collections.synchronizedMap(new HashMap<>());
}
}
list是线程不安全的ArrayList,通过Collections中的synchronizedList方法。
将普通的ArrayList封装成了线程安全的List。之后对list的操作就是安全的。
set、map同样有对应的方法将不安全的集合封装成线程安全的集合。
需要注意的是:这个操作只保证了单个方法(如add, get)是原子的。对于复合操作(比如"检查是否存在,如果不存在则添加"),仍然需要自己使用synchronized关键字来保证整个操作的原子性。
四、创建不可变和空集合
不可变
有时候,我们想创建一个不可变的集合,这个集合不让修改。就可以用Collections中的unmodifiableXXX()方法创建只读的集合。

java
package com.lazy.snail.day30;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName Day30Demo6
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 16:56
* @Version 1.0
*/
public class Day30Demo6 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("懒惰蜗牛");
List<String> unmodifiableList = Collections.unmodifiableList(list);
//unmodifiableList.add("lazysnail");
unmodifiableList.remove("懒惰蜗牛");
}
}
如果想对这样的集合进行add或remove等操作,就会抛出UnsupportedOperationException异常。
从Java9开始,List, Set, Map接口自身提供了更方便的静态of()方法来直接创建真正的不可变集合。
这种方式在实际开发中更加常见。
为什么说是真正的不可变呐。回看上面的案例,稍作一下修改:
java
package com.lazy.snail.day30;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName Day30Demo6
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 16:56
* @Version 1.0
*/
public class Day30Demo6 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("懒惰蜗牛");
List<String> unmodifiableList = Collections.unmodifiableList(list);
System.out.println("原集合修改前的unmodifiableList:" + unmodifiableList);
list.add("lazysnail");
System.out.println("原集合修改后的unmodifiableList:" + unmodifiableList);
}
}
可以看出,unmodifiableList并没有改变原集合的可变性,只是加了一层只读外壳。
如果原集合一旦被外部引用持有,仍然可以修改。
而List.of(...) 是真正的不可变对象,内部结构不允许任何修改操作。
所有变更操作(add、remove、set)都会直接抛UnsupportedOperationException。
java
List<String> list1 = List.of("懒惰蜗牛");
list1.add("lazysnail");
"list1.add("lazysnail");"就会抛出UnsupportedOperationException。
空集合
很多返回集合的方法,如果直接返回null,保不准什么时候就会报空指针异常。
实际开发的时候,最好还是返回一个空集合。
Collections的emptyList(), emptySet(), emptyMap()就派上了用场。
java
package com.lazy.snail.day30;
import java.util.Collections;
import java.util.List;
/**
* @ClassName Day30Demo7
* @Description TODO
* @Author lazysnail
* @Date 2025/7/7 17:14
* @Version 1.0
*/
public class Day30Demo7 {
public static List<String> findUsers(String name) {
// if 找到
// else
return Collections.emptyList();
}
}
可能你会说,为什么不直接return的时候直接new ArrayList<>()。
每次return的时候,都去new一个对象,还是有开销的,能省则省。
Collections的emptyList()返回的是一个不可变的、线程安全的、可被序列化的空集合。
每次返回的都是同一个静态实例。

五、其他
Collections中还有很多其他的小工具:
reverse(List<?> list): 反转List中元素的顺序。
shuffle(List<?> list): 随机打乱List中元素的顺序。
max(Collection coll) / min(Collection coll): 找到集合中的最大/最小元素。
fill(List<?> list, T obj): 用指定元素替换List中的所有元素。
更多的方法参见API文档:
Collections (Java SE 17 & JDK 17)docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collections.html
结语
Collections中有非常多关于集合的操作工具。
今天我们只是一起看了一下排序、查找、线程安全、创建不可变集合等常见的功能。
这些工具都能够在实际开发中帮我们让代码更加简洁,让集合操作更加便捷高效。
Collections里的方法不需要取死记硬背,在写代码的时候,如果涉及操作集合,
可以翻看一下API文档,如果有合适的工具,使用即可。
下一篇预告
Day31 | Lambda表达式与函数式接口
如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!
更多文章请关注我的公众号《懒惰蜗牛工坊》