乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

Collections 工具类详解

本章定位:掌握 java.util.Collections 工具类的全部核心静态方法——排序、查找、反转、打乱、统计、同步包装、不可变包装和类型安全包装。这些方法是操作集合的"瑞士军刀"。在飞翔科技的工资报表排序、季度数据打乱(模拟测试)、线程安全改造、防止堆污染等场景中广泛使用。本章由白歌系统讲解工具类设计思想,小崔用 Lambda 大幅简化代码,孔蓝发现了 unchecked 堆污染漏洞,Frank 和大翔在月度报告中使用 sort 和 binarySearch。


定义速览表

属性说明
全类名java.util.Collections
构造器private —— 禁止实例化(纯工具类)
方法性质全部为 public static 方法
设计模式装饰器模式(synchronized/unmodifiable/checked 包装)
排序算法List.sort() → TimSort(对象数组);Arrays.sort() → DualPivotQuickSort(基本类型数组)
二分查找基于索引(ArrayList)或迭代器(LinkedList),要求 List 已升序排列
线程安全包装synchronizedXxx() —— 方法级 synchronized,迭代需手动加锁
不可变包装unmodifiableXxx() —— 写操作抛 UnsupportedOperationException,浅不可变
与 Collection 区别Collection 是接口(定义集合通用操作);Collections 是工具类(操作集合的静态方法)

方法分类速查表

排序与查找

方法描述
sort(List<T>)按自然顺序排序(元素须实现 Comparable)
sort(List<T>, Comparator<? super T>)按指定 Comparator 排序
binarySearch(List<? extends Comparable<? super T>>, T)二分查找(List 必须已升序排序)
binarySearch(List<? extends T>, T, Comparator<? super T>)带 Comparator 的二分查找
reverse(List<?>)反转列表
shuffle(List<?>)随机打乱(使用默认随机源)
shuffle(List<?>, Random)随机打乱(指定随机源,可复现)
swap(List<?>, int, int)交换两个位置的元素
fill(List<? super T>, T)用指定元素填充整个列表
copy(List<? super T>, List<? extends T>)将源列表复制到目标列表(目标长度须 >= 源)
rotate(List<?>, int distance)循环移动元素(正数右移、负数左移)
replaceAll(List<T>, T oldVal, T newVal)替换列表中所有指定旧值为新值

极值、频率与统计

方法描述
min(Collection<? extends T>)返回自然顺序下的最小元素
min(Collection<? extends T>, Comparator<? super T>)按指定 Comparator 返回最小
max(Collection<? extends T>)返回自然顺序下的最大元素
max(Collection<? extends T>, Comparator<? super T>)按指定 Comparator 返回最大
frequency(Collection<?>, Object)统计指定元素在集合中出现的次数

线程安全包装

方法返回类型说明
synchronizedCollection(Collection<T>)Collection<T>线程安全的 Collection
synchronizedList(List<T>)List<T>线程安全的 List
synchronizedSet(Set<T>)Set<T>线程安全的 Set
synchronizedSortedSet(SortedSet<T>)SortedSet<T>线程安全的 SortedSet
synchronizedMap(Map<K,V>)Map<K,V>线程安全的 Map
synchronizedSortedMap(SortedMap<K,V>)SortedMap<K,V>线程安全的 SortedMap

不可变包装

方法返回类型说明
unmodifiableCollection(Collection<? extends T>)Collection<T>只读 Collection
unmodifiableList(List<? extends T>)List<T>只读 List
unmodifiableSet(Set<? extends T>)Set<T>只读 Set
unmodifiableMap(Map<? extends K, ? extends V>)Map<K,V>只读 Map
unmodifiableSortedMap(SortedMap<K, ? extends V>)SortedMap<K,V>只读 SortedMap

类型安全包装

方法说明
checkedCollection(Collection<E>, Class<E>)运行时检查元素类型,防止堆污染
checkedList(List<E>, Class<E>)运行时检查 List 元素类型
checkedSet(Set<E>, Class<E>)运行时检查 Set 元素类型
checkedMap(Map<K,V>, Class<K>, Class<V>)运行时检查 Map 键值类型

特殊集合与工具

方法描述
emptyList() / emptySet() / emptyMap()返回不可变的空集合(全局单例共享)
singleton(T) / singletonList(T) / singletonMap(K,V)返回仅含一个元素的不可变集合
nCopies(int n, T o)返回包含 n 个相同元素副本的不可变 List
reverseOrder()返回自然顺序的逆序 Comparator
reverseOrder(Comparator<T>)返回指定 Comparator 的逆序
newSetFromMap(Map<E,Boolean>)从 Map 创建 Set(用于 ConcurrentHashMap → ConcurrentHashSet)
list(Enumeration<T>)将遗留 Enumeration 转为 ArrayList
enumeration(Collection<T>)返回传统 Enumeration 适配器
disjoint(Collection<?>, Collection<?>)判断两个集合是否完全无交集
indexOfSubList(List<?>, List<?>)返回子列表首次出现位置
lastIndexOfSubList(List<?>, List<?>)返回子列表最后出现位置

核心原理

sort() 与排序算法选择

TimSort vs DualPivotQuickSort:

维度TimSort(对象排序)DualPivotQuickSort(基本类型排序)
算法类型归并排序 + 插入排序(混合)双轴快速排序
时间复杂度最好 O(n),最坏 O(n log n)平均 O(n log n)
空间复杂度O(n)O(log n)
稳定性稳定(相等元素相对顺序不变)不稳定
适用数据部分有序数据极快完全随机数据快
原因对象排序需要稳定性(多次排序不破坏前一次顺序)基本类型无"同一性"概念,追求速度

binarySearch 的内部两种实现

关键:binarySearch 要求列表已按升序排序(使用与排序时相同的 Comparator),否则结果不可预测。返回值 < 0 时,-(index + 1) 给出了元素"应插入位置"。

三种包装器的装饰器模式

三者都采用装饰器模式(Decorator Pattern):包装对象实现相同的接口,在方法调用前后添加额外逻辑(同步/禁止修改/类型检查),最终委托给原集合。


完整代码示例

示例一:飞翔科技月度工资报表 —— 排序与查找

import java.util.*;

/**
 * 场景:月初,大翔要求对飞翔科技各部门提交的薪资报表进行排序和统计。
 * 白歌使用 Collections 工具类完成排序、查找、反转和频率统计。
 * Frank 要求报表能按部门索引快速定位特定工资。
 */
public class FeiXiangSalaryReport {

    /** 员工工资条目 */
    static class SalaryEntry implements Comparable<SalaryEntry> {
        final String name;
        final String department;
        final double salary;

        SalaryEntry(String name, String department, double salary) {
            this.name = name;
            this.department = department;
            this.salary = salary;
        }

        // 自然排序:按工资升序(Comparable)
        @Override
        public int compareTo(SalaryEntry o) {
            return Double.compare(this.salary, o.salary);
        }

        @Override
        public String toString() {
            return String.format("%s(%s, ¥%.0f)", name, department, salary);
        }
    }

    public static void main(String[] args) {
        // ========== 1. 原始工资数据 ==========
        List<SalaryEntry> salaries = new ArrayList<>(Arrays.asList(
            new SalaryEntry("大翔", "管理层", 80000),
            new SalaryEntry("白歌", "技术部", 35000),
            new SalaryEntry("小崔", "技术部", 12000),
            new SalaryEntry("孔蓝", "测试部", 15000),
            new SalaryEntry("Frank", "市场部", 22000),
            new SalaryEntry("黄俪", "人事部", 18000),
            new SalaryEntry("赵鸣", "技术部", 35000),
            new SalaryEntry("孙鹤", "技术部", 12000)
        ));

        System.out.println("=== 飞翔科技月度工资报表 ===\n");
        System.out.println("原始数据: " + salaries.size() + " 条记录");

        // ========== 2. sort:按自然顺序(升序) ==========
        Collections.sort(salaries);
        System.out.println("\n--- 按工资升序(自然排序) ---");
        salaries.forEach(s -> System.out.println("  " + s));

        // ========== 3. sort + reverseOrder:降序 ==========
        Collections.sort(salaries, Collections.reverseOrder());
        System.out.println("\n--- 按工资降序 ---");
        salaries.forEach(s -> System.out.println("  " + s));

        // 恢复升序以便后续操作
        Collections.sort(salaries);

        // ========== 4. binarySearch:二分查找 ==========
        System.out.println("\n--- 二分查找(List 已按升序排列) ---");
        SalaryEntry target = new SalaryEntry("", "", 22000);
        int idx = Collections.binarySearch(salaries, target);
        System.out.println("查找 ¥22000: 索引 = " + idx + " → " + salaries.get(idx));

        // 查找不存在的工资
        int idx2 = Collections.binarySearch(salaries, new SalaryEntry("", "", 20000));
        System.out.println("查找 ¥20000: 索引 = " + idx2
            + " (不存在, 应插入位置: " + (-(idx2 + 1)) + ")");

        // ========== 5. min / max ==========
        SalaryEntry minSalary = Collections.min(salaries);
        SalaryEntry maxSalary = Collections.max(salaries);
        System.out.println("\n--- 极值 ---");
        System.out.println("最低工资: " + minSalary);
        System.out.println("最高工资: " + maxSalary);

        // ========== 6. frequency:频率统计 ==========
        SalaryEntry query = new SalaryEntry("", "", 12000);
        int freq = Collections.frequency(salaries, query);
        System.out.println("\n--- 频率统计 ---");
        System.out.println("工资恰好为 ¥12000 的人数: " + freq);

        // ========== 7. reverse:反转 ==========
        System.out.println("\n--- 反转列表 ---");
        Collections.reverse(salaries);
        System.out.println("反转后前三: " + salaries.subList(0, 3));

        // 恢复升序
        Collections.sort(salaries);

        // ========== 8. shuffle + rotate ==========
        System.out.println("\n--- 随机打乱 + 循环移动 ---");
        // 使用固定种子确保演示可复现
        Collections.shuffle(salaries, new Random(42));
        System.out.println("打乱后: " + salaries.subList(0, 5) + "...");

        Collections.rotate(salaries, 3);  // 右移 3 位
        System.out.println("右移3位后: " + salaries.subList(0, 5) + "...");

        // ========== 9. 按部门排序(自定义 Comparator) ==========
        System.out.println("\n--- 按部门 + 姓名排序 ---");
        Collections.sort(salaries, Comparator
            .comparing((SalaryEntry s) -> s.department)
            .thenComparing(s -> s.name));
        salaries.forEach(s -> System.out.println("  " + s));
    }
}
=== 飞翔科技月度工资报表 ===

原始数据: 8 条记录

--- 按工资升序(自然排序) ---
  小崔(技术部, ¥12000)
  孙鹤(技术部, ¥12000)
  孔蓝(测试部, ¥15000)
  黄俪(人事部, ¥18000)
  Frank(市场部, ¥22000)
  白歌(技术部, ¥35000)
  赵鸣(技术部, ¥35000)
  大翔(管理层, ¥80000)

--- 按工资降序 ---
  大翔(管理层, ¥80000)
  白歌(技术部, ¥35000)
  赵鸣(技术部, ¥35000)
  Frank(市场部, ¥22000)
  黄俪(人事部, ¥18000)
  孔蓝(测试部, ¥15000)
  小崔(技术部, ¥12000)
  孙鹤(技术部, ¥12000)

--- 二分查找(List 已按升序排列) ---
查找 ¥22000: 索引 = 4 → Frank(市场部, ¥22000)
查找 ¥20000: 索引 = -5 (不存在, 应插入位置: 4)

--- 极值 ---
最低工资: 小崔(技术部, ¥12000)
最高工资: 大翔(管理层, ¥80000)

--- 频率统计 ---
工资恰好为 ¥12000 的人数: 2

--- 反转列表 ---
反转后前三: [大翔(管理层, ¥80000), 赵鸣(技术部, ¥35000), 白歌(技术部, ¥35000)]

--- 随机打乱 + 循环移动 ---
打乱后: [赵鸣(技术部, ¥35000), 孔蓝(测试部, ¥15000), Frank(市场部, ¥22000), 大翔(管理层, ¥80000), 白歌(技术部, ¥35000)]...
右移3位后: [黄俪(人事部, ¥18000), 小崔(技术部, ¥12000), 赵鸣(技术部, ¥35000), 孔蓝(测试部, ¥15000), Frank(市场部, ¥22000)]...

--- 按部门 + 姓名排序 ---
  大翔(管理层, ¥80000)
  Frank(市场部, ¥22000)
  黄俪(人事部, ¥18000)
  孔蓝(测试部, ¥15000)
  白歌(技术部, ¥35000)
  孙鹤(技术部, ¥12000)
  小崔(技术部, ¥12000)
  赵鸣(技术部, ¥35000)

示例二:线程安全包装与不可变包装 —— 白歌的安全改造

import java.util.*;

/**
 * 场景:飞翔科技的共享配置数据被多个线程同时访问。
 * 白歌要求小崔对配置列表进行线程安全改造,并对只读配置进行不可变保护。
 * 孔蓝负责验证线程安全性和不可变性的边界情况。
 */
public class FeiXiangCollectionsWrapper {

    /** 模拟飞翔科技的在线用户列表(多线程共享) */
    static class OnlineUserTracker {
        // 线程安全的 List
        private final List<String> onlineUsers;

        public OnlineUserTracker() {
            // synchronizedList 包装普通的 ArrayList
            this.onlineUsers = Collections.synchronizedList(new ArrayList<>());
        }

        /** 用户上线 */
        public void login(String username) {
            onlineUsers.add(username);
        }

        /** 用户下线 */
        public void logout(String username) {
            onlineUsers.remove(username);
        }

        /** 获取在线用户快照(安全遍历) */
        public List<String> getOnlineUserSnapshot() {
            // 遍历时必须手动加锁!
            synchronized (onlineUsers) {
                return new ArrayList<>(onlineUsers);  // 返回副本
            }
        }

        /** 获取在线人数 */
        public int getOnlineCount() {
            return onlineUsers.size();
        }
    }

    /** 飞翔科技的系统配置(只读) */
    static class SystemConfig {
        private final List<String> adminNames;
        private final Map<String, String> settings;

        public SystemConfig() {
            // 内部使用可变的集合
            List<String> admins = new ArrayList<>();
            admins.add("大翔");
            admins.add("白歌");

            Map<String, String> configs = new HashMap<>();
            configs.put("company", "飞翔科技");
            configs.put("version", "2.0.0");
            configs.put("maxLoginAttempts", "5");

            // 暴露不可变视图
            this.adminNames = Collections.unmodifiableList(admins);
            this.settings = Collections.unmodifiableMap(configs);
        }

        public List<String> getAdminNames() { return adminNames; }
        public Map<String, String> getSettings() { return settings; }
    }

    public static void main(String[] args) {
        // ========== 一、线程安全包装 ==========
        System.out.println("=== 线程安全包装演示 ===");

        OnlineUserTracker tracker = new OnlineUserTracker();

        // 模拟多用户上线
        tracker.login("大翔");
        tracker.login("白歌");
        tracker.login("小崔");
        tracker.login("孔蓝");

        System.out.println("当前在线: " + tracker.getOnlineUserSnapshot());
        System.out.println("在线人数: " + tracker.getOnlineCount());

        tracker.logout("小崔");
        System.out.println("小崔下线后: " + tracker.getOnlineUserSnapshot());

        // 验证线程安全:多线程同时操作
        System.out.println("\n--- 多线程并发登录测试 ---");
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                tracker.login("用户A-" + i);
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                tracker.login("用户B-" + i);
            }
        });
        t1.start(); t2.start();
        try { t1.join(); t2.join(); } catch (InterruptedException e) {}
        System.out.println("并发操作后在线人数: " + tracker.getOnlineCount()
            + " (期望: " + (4 - 1 + 200) + ")");

        // ========== 二、不可变包装 ==========
        System.out.println("\n=== 不可变包装演示 ===");

        SystemConfig config = new SystemConfig();
        System.out.println("管理员列表: " + config.getAdminNames());
        System.out.println("系统配置: " + config.getSettings());

        // 尝试修改不可变列表
        try {
            config.getAdminNames().add("Frank");  // 应该抛异常
        } catch (UnsupportedOperationException e) {
            System.out.println("修改被拦截: " + e.getClass().getSimpleName()
                + " —— 不可变集合不允许修改");
        }

        // 读取依然正常
        System.out.println("管理员列表(仍为只读): " + config.getAdminNames());

        // ========== 三、特殊集合 ==========
        System.out.println("\n=== 特殊集合 ===");

        // 空集合(共享单例)
        List<String> emptyList = Collections.emptyList();
        System.out.println("空列表: " + emptyList + " (isEmpty: " + emptyList.isEmpty() + ")");

        // 单元素集合(不可变)
        Set<String> ceo = Collections.singleton("大翔-CEO");
        System.out.println("CEO 集合: " + ceo);

        // nCopies
        List<String> pendingStatus = Collections.nCopies(5, "待审批");
        System.out.println("5 个待审批: " + pendingStatus);

        // ========== 四、Comparator 工具方法 ==========
        System.out.println("\n=== Comparator 工具 ===");

        List<String> names = new ArrayList<>(Arrays.asList(
            "白歌", "小崔", "大翔", "孔蓝", "Frank"
        ));

        // 自然顺序的逆序
        Collections.sort(names, Collections.reverseOrder());
        System.out.println("逆字典序: " + names);

        // 恢复自然顺序
        Collections.sort(names);
        System.out.println("自然字典序: " + names);

        // ========== 五、disjoint:判断无交集 ==========
        System.out.println("\n=== 集合运算 ===");
        Set<String> techDept = new HashSet<>(Arrays.asList("白歌", "小崔", "赵鸣", "孙鹤"));
        Set<String> mgmtDept = new HashSet<>(Arrays.asList("大翔", "Frank"));
        Set<String> allDept = new HashSet<>(Arrays.asList("白歌", "小崔", "大翔", "Frank", "孔蓝"));

        System.out.println("技术部与管理层无交集? "
            + Collections.disjoint(techDept, mgmtDept));  // true

        System.out.println("技术部与全员有交集? "
            + Collections.disjoint(techDept, allDept));   // false
    }
}
=== 线程安全包装演示 ===
当前在线: [大翔, 白歌, 小崔, 孔蓝]
在线人数: 4
小崔下线后: [大翔, 白歌, 孔蓝]

--- 多线程并发登录测试 ---
并发操作后在线人数: 203 (期望: 203)

=== 不可变包装演示 ===
管理员列表: [大翔, 白歌]
系统配置: {version=2.0.0, company=飞翔科技, maxLoginAttempts=5}
修改被拦截: UnsupportedOperationException —— 不可变集合不允许修改
管理员列表(仍为只读): [大翔, 白歌]

=== 特殊集合 ===
空列表: [] (isEmpty: true)
CEO 集合: [大翔-CEO]
5 个待审批: [待审批, 待审批, 待审批, 待审批, 待审批]

=== Comparator 工具 ===
逆字典序: [白歌, 小崔, 孔蓝, 大翔, Frank]
自然字典序: [Frank, 小崔, 大翔, 孔蓝, 白歌]

=== 集合运算 ===
技术部与管理层无交集? true
技术部与全员有交集? false

易错场景

反例一:未排序就调用 binarySearch

小崔第一次使用二分查找时,忘了先排序:

// ❌ 错误:未排序的 list 上调用 binarySearch,结果不可预测
List<Integer> list = Arrays.asList(5, 2, 8, 1, 3);
int idx = Collections.binarySearch(list, 2);
System.out.println(idx);  // 可能是任何值(正数/负数),完全不可靠!

纠正:

// ✅ 正确:先排序再二分查找
Collections.sort(list);  // [1, 2, 3, 5, 8]
int idx = Collections.binarySearch(list, 2);  // 1,正确

反例二:synchronizedList 遍历时未手动加锁

孔蓝发现小崔在多线程遍历时没有手动同步:

// ❌ 错误:遍历 synchronizedList 时未手动同步
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.add("大翔"); syncList.add("白歌"); syncList.add("小崔");

// 线程1:用 for-each 遍历(内部使用迭代器)
new Thread(() -> {
    for (String s : syncList) {
        System.out.println(s);  // 可能抛 ConcurrentModificationException
        try { Thread.sleep(100); } catch (InterruptedException e) {}
    }
}).start();

// 线程2:添加
new Thread(() -> {
    syncList.add("孔蓝");  // 虽然 add 是同步的,但无法保护整个迭代过程
}).start();

纠正:

// ✅ 正确:遍历时手动加锁
synchronized (syncList) {  // 锁住整个遍历过程
    for (String s : syncList) {
        System.out.println(s);
    }
}

// ✅ 或使用迭代器 + 手动加锁
synchronized (syncList) {
    Iterator<String> it = syncList.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

反例三:unmodifiableList 是浅不可变,元素内部状态仍可修改

// ❌ 误区:以为 unmodifiableList 连元素状态都保护了
class MutableEmployee {
    String name;
    MutableEmployee(String name) { this.name = name; }
    void setName(String name) { this.name = name; }
    @Override
    public String toString() { return name; }
}

List<MutableEmployee> list = new ArrayList<>();
list.add(new MutableEmployee("小崔"));
list.add(new MutableEmployee("白歌"));

List<MutableEmployee> unmodList = Collections.unmodifiableList(list);

// 不能修改 List 结构(抛异常)
// unmodList.add(new MutableEmployee("孔蓝"));  // ✗ UnsupportedOperationException

// 但可以修改元素内部状态!
unmodList.get(0).setName("小崔(已修改)");  // ✓ 成功了!
System.out.println(unmodList);  // [小崔(已修改), 白歌]

纠正:

// ✅ 如需深度不可变,元素类也应该是不可变的
class ImmutableEmployee {
    private final String name;
    public ImmutableEmployee(String name) { this.name = name; }
    public String getName() { return name; }
    // 无 setter 方法 —— 完全不可变
}

// ✅ 或使用 Guava 的 ImmutableList.copyOf(深拷贝)
// ImmutableList<Employee> immutable = ImmutableList.copyOf(original);

反例四:copy 方法目标列表不够长

// ❌ 错误:copy 要求目标列表长度 >= 源列表长度
List<String> src = Arrays.asList("A", "B", "C", "D");
List<String> dest = new ArrayList<>(2);  // 长度不够!
Collections.copy(dest, src);  // IndexOutOfBoundsException!

纠正:

// ✅ 方案一:预先填充够长度的元素
List<String> dest = new ArrayList<>(Collections.nCopies(src.size(), ""));
Collections.copy(dest, src);  // OK

// ✅ 方案二:直接用构造器(推荐)
List<String> dest = new ArrayList<>(src);  // 更简单直接

面试考点

Q1:Collections 和 Collection 的区别?

Collection 是集合框架的顶层接口(java.util.Collection),定义了 add、remove、contains 等集合通用操作,List 和 Set 都继承自它。Collections 是工具类(java.util.Collections),全部由静态方法组成(private 构造器),提供排序、查找、线程安全包装、不可变包装等操作集合的方法。两者的关系是"接口 vs 工具类",没有继承关系——一个是"数据结构是什么",一个是"我能对数据结构做什么"。

Q2:Collections.sort 底层使用什么排序算法?为什么对象和基本类型用不同算法?

JDK 8 中 Collections.sort(list) 委托给 List.sort(),底层对对象数组使用 TimSort(自适应混合排序:归并 + 插入,稳定排序,最坏 O(n log n),最好 O(n))。对基本类型数组(通过 Arrays.sort()),使用 DualPivotQuickSort(双轴快速排序,不稳定排序)。

原因:对象排序需要稳定性——当用户先按部门再按工资排序时,稳定排序保证同一部门内工资顺序不变。基本类型没有"同一性"概念(1 就是 1,没有两个不同的 1),不需要稳定性,因此使用速度更快但不稳定的快排。

Q3:Collections.synchronizedList 和 Vector 的区别?synchronizedList 和 CopyOnWriteArrayList 如何选择?

维度synchronizedListVectorCopyOnWriteArrayList
实现方式装饰器模式(包装任意 List)自身是 List 实现(数组)写时复制(新数组)
出现时间JDK 1.2JDK 1.0JDK 1.5
迭代加锁需要手动加锁需要(或 Enumeration)不需要(快照)
读性能需竞争锁需竞争锁无锁,极高
写性能需竞争锁需竞争锁极差(复制整个数组)
适用场景读写均衡不推荐(遗留类)读多写少

读多写少(如配置信息、黑名单)→ CopyOnWriteArrayList;读写均衡 → Collections.synchronizedList 或 ConcurrentLinkedQueue;新项目不推荐 Vector。

Q4:unmodifiableList 和真正的不可变集合有什么不同?

unmodifiableList 返回的是原集合的只读视图: ① 浅不可变:只禁止修改 List 结构(add/remove/set),不保护元素内部状态(仍可通过 get(i).setXxx() 修改元素字段); ② 非独立:如果原集合被修改(通过原始引用),unmodifiableList 视图会反映变化——它只是原集合的一个"查看窗口"; ③ 非深拷贝:不复制元素本身。

真正的不可变集合(如 Guava 的 ImmutableList): ① 深拷贝:创建时复制所有元素,与原集合完全独立; ② 深不可变:通常要求元素类也是不可变的; ③ Java 9+ 提供了 List.of() / Set.of() 工厂方法创建真正的不可变集合。

在 JDK 8 中,如果需要真正的不可变性,应使用 new ArrayList<>(original) 复制一份后用 unmodifiableList 包装,并确保元素类不可变。

Q5:Collections.emptyList() 有什么优势?为什么不用 new ArrayList<>() 返回空列表?

emptyList() 返回的是一个不可变的空列表单例——所有调用共享同一个实例。优势: ① 零内存分配:全局只有一个空列表对象,多次调用不产生新对象; ② 线程安全:不可变对象天然线程安全; ③ 明确语义:这是一个"故意为空的列表"而非"忘了填充的列表"; ④ 防御性编程:调用方无法修改返回的空列表(抛 UnsupportedOperationException),避免了意外修改。

在生产代码中,当方法返回"无结果"时,返回 Collections.emptyList() 比返回 null 更好——调用方无需做 null 检查,可以直接使用 for-each 遍历(空遍历不会抛 NPE)。

Q6:Collections.checkedList 解决了什么问题?

checkedList 在运行时提供类型安全检查,防止泛型擦除导致的"堆污染"(heap pollution)。由于 Java 的泛型信息在编译后会被擦除,可以通过原始类型(raw type)绕过编译期检查:

List<String> list = new ArrayList<>();
List raw = list;          // 原始类型
raw.add(123);             // 放入 Integer,编译不报错!
String s = list.get(0);   // ClassCastException 在运行时才暴露

使用 CheckedList 包装后:List<String> list = Collections.checkedList(new ArrayList<>(), String.class);,即使在运行时通过原始类型绕过编译检查,add 方法也会调用 type.isInstance(e) 进行类型验证,在插入时立即抛出异常(fail-fast),而不是等后续读取时才暴露。

上一页
TreeSet 与 Comparable