Day30 | Java集合框架之Collections工具类

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表达式与函数式接口

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》

相关推荐
Java天梯之路6 小时前
Spring Boot 钩子全集实战(二):`SpringApplicationRunListener.starting()` 详解
java·spring·面试
Robet6 小时前
Zig 模块和C 头文件包含
后端
WXG10116 小时前
【Flask-9】加载视频流
后端·python·flask
忘带键盘了6 小时前
eclipse配置
java·ide·eclipse
程序员爱钓鱼6 小时前
Node.js 编程实战:MongoDB 基础与 Mongoose 入门
后端·node.js·trae
柒儿吖6 小时前
深度实战:Rust交叉编译适配OpenHarmony PC——terminal_size完整适配案例
后端·rust·harmonyos
Aevget6 小时前
知名Java开发工具IntelliJ IDEA v2025.3正式上线——开发效率全面提升
java·ide·人工智能·intellij-idea·开发工具
coderCatIce6 小时前
Redis-常见 Java 客户端
redis·后端
程序员ggbond6 小时前
springboot的一些应用总结
后端