一、核心认知类考点
1、Collections 和 Arrays 工具类的核心特点?
- 两者均位于
java.util包下,且都是不可实例化的工具类(构造方法私有,所有方法为 static); Collections专注于操作 / 扩展Collection接口(List/Set/Queue)及其实现类;Arrays专注于数组的操作(排序、查找、转换、填充等),同时提供数组与集合的互转方法。
2、为什么这两个类不能被实例化?
2.1、工具类的设计目标是提供静态方法,无需创建实例;
2.2、源码层面:构造方法被声明为 private 且抛出异常,防止通过反射实例化;
java
// Collections 源码示例
private Collections() {
throw new UnsupportedOperationException();
}
2.3、符合 Java 工具类的设计规范(如 Math 类同理)
3、核心功能对比
| 维度 | Collections 工具类 | Arrays 工具类 |
|---|---|---|
| 操作对象 | List/Set/Queue 等集合 | 各种类型的数组(基本类型 + 引用类型) |
| 核心功能 | 排序、查找、同步化、不可变集合、批量添加等 | 排序、查找、填充、数组转集合、复制、比较等 |
| 线程安全 | 提供同步集合包装方法(如 synchronizedList) | 无线程安全相关方法 |
| 空值处理 | 部分方法(如 sort)不支持 null 元素 | 支持数组中存在 null(如 sort 引用类型数组) |
二、Collections 工具类
1、Collections与 Collection 接口的区别
| 概念 | 本质 | 核心作用 |
|---|---|---|
Collection |
接口 | 定义集合的通用操作规范 |
Collections |
工具类 | 提供操作 Collection 的静态方法 |
- 面试陷阱:避免将「工具类 Collections」与「集合顶层接口 Collection」混淆,这是入门级易错点。
2、高频 API 考点
1. 排序 & 查找
核心方法
java
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));
// 1. 自然排序(元素需实现 Comparable 接口)
Collections.sort(list); // 结果:[1,2,3]
// 2. 自定义排序(Comparator 比较器)
Collections.sort(list, (a, b) -> b - a); // 降序:[3,2,1]
// 3. 二分查找(前提:List 已升序排序)
int index = Collections.binarySearch(list, 2);
面试考点
- 考点 1:
binarySearch返回值规则(重中之重):- 找到元素:返回元素索引;
- 未找到元素:返回
-(插入点) - 1(插入点指 "元素应插入的位置",例如插入点为 0 → 返回 - 1,插入点为 2 → 返回 - 3);
- 考点 2:
sort方法的异常场景:List 中包含null元素时,会抛出NullPointerException; - 延伸考点:手写实现
Collections.sort的核心逻辑。
2、不可变集合
核心方法
java
// 1. 空不可变集合(复用单例,无扩容开销)
List<String> emptyList = Collections.emptyList();
Set<Integer> emptySet = Collections.emptySet();
// 2. 单元素不可变集合
List<String> singletonList = Collections.singletonList("test");
// 3. 包装不可变集合(浅不可变)
List<String> originList = new ArrayList<>();
List<String> unmodifiableList = Collections.unmodifiableList(originList);
面试考点
- 考点 1:三种不可变集合的区别:
emptyList()/singletonList():底层是私有静态内部类,无扩容 / 内存开销,效率最高;unmodifiableList():仅为 "包装器",原集合修改会同步反映到包装集合中(浅不可变);
- 考点 2:与 JDK 9+
List.of()的对比(延伸题):Collections.unmodifiableList():浅不可变,原集合修改会影响包装集合;List.of():深不可变,创建后无法修改,且不允许 null 元素;
- 考点 3:不可变集合的应用场景:常量定义、方法返回值(防止外部修改)、线程安全(只读场景)。
3、同步集合(线程安全考点)
核心方法
java
// 将非线程安全集合包装为线程安全
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
考点 1:同步集合的局限性:
- 仅实现「方法级同步」(方法加
synchronized),遍历集合时需手动加锁 ,否则可能抛出ConcurrentModificationException;
考点 2:与并发集合的对比(高频延伸题):
Collections.synchronizedList:性能低(全局锁),适合低并发场景;CopyOnWriteArrayList(JUC 包):读写分离,适合读多写少场景,性能更高;
4. 其他高频方法
| 方法 | 作用 | 面试注意点 |
|---|---|---|
Collections.reverse(list) |
反转集合元素 | - |
Collections.shuffle(list) |
随机打乱元素(洗牌算法) | 可用于抽奖 / 随机排序场景 |
Collections.swap(list, i, j) |
交换指定位置元素 | - |
Collections.max(list) |
获取最大值 | 元素需实现 Comparable 接口 |
Collections.addAll(list, e1, e2) |
批量添加 | 比集合自身的 addAll 更高效 |
Collections.fill(list, obj) |
填充元素 | 将集合所有元素替换为指定对象 |
3、面试高频问题和易错陷阱
1. 高频问答
Collections 提供的同步集合是真正的线程安全吗?
- 答案:不完全是。方法级同步仅保证单个方法的原子性,复合操作(如 "检查 - 再操作"、遍历)需手动加锁,否则仍会出现线程安全问题。
为什么 Collections.emptyList () 比 new ArrayList () 更高效?
- 答案:
emptyList()返回的是静态单例对象(底层无数组、无扩容),而new ArrayList()会初始化长度为 10 的数组,存在内存开销。
Collections.sort () 的底层实现?
- 答案:底层调用
Arrays.sort(),先将 List 转为数组排序,再将排序后的数组写回 List;JDK 8 中,引用类型数组用 TimSort(归并 + 插入),基本类型用双轴快排。
2. 易错陷阱
- 调用
unmodifiableList()后修改原集合,认为包装集合不会变(实际会同步变化); binarySearch未排序就使用,认为返回 - 1 表示未找到(实际返回值规则复杂);- 遍历
synchronizedList时不加锁,导致并发修改异常; Collections.sort()传入包含 null 的 List,未捕获 NPE 异常。
三、Arrays工具类
1、基础特性
考点 1:工具类的核心设计特点
Arrays是不可实例化 的静态工具类:构造方法被私有化(源码层面),防止创建对象,所有方法均为static;- 操作对象:支持所有类型数组(基本类型数组:int []/char [] 等;引用类型数组:String []/Object [] 等);
- 核心定位:提供数组的排序、查找、复制、填充、比较、转集合等一站式操作,替代手动遍历实现。
考点 2:与数组原生操作的区别
| 操作场景 | 原生数组操作(for 循环) | Arrays 工具类 |
|---|---|---|
| 排序 | 需手动实现排序算法 | 内置优化算法 |
| 数组比较 | 需遍历逐个对比 | equals() 一键对比 |
| 数组转字符串 | 需遍历拼接 | toString() 直接输出 |
| 数组复制 | 需手动遍历赋值 | copyOf() 高效实现 |
Arrays 工具类的设计目标是简化数组操作、提升性能 (底层调用 native 方法如 System.arraycopy)。
2、高频 API 考点
1. 排序 & 查找
核心方法
java
int[] arr = {3, 1, 2, 5, 4};
// 1. 全数组排序(默认升序)
Arrays.sort(arr); // 结果:[1,2,3,4,5]
// 2. 范围排序(左闭右开区间)
Arrays.sort(arr, 0, 3); // 仅排序[0,3):[1,2,3,5,4]
// 3. 二分查找(前提:数组已升序排序)
int index = Arrays.binarySearch(arr, 3); // 找到,返回2
int notFoundIndex = Arrays.binarySearch(arr, 6); // 未找到,返回-6
面试考点
- 考点 1:排序算法的底层实现(重中之重):
- 基本类型数组(int []/char [] 等):使用双轴快速排序(Dual-Pivot QuickSort),特点:效率高、不稳定;
- 引用类型数组(String []/Object [] 等):使用TimSort(归并排序 + 插入排序),特点:稳定、适配真实场景的有序子序列;
- 考点 2:
binarySearch返回值规则(与Collections.binarySearch一致):- 找到元素:返回元素索引;
- 未找到元素:返回
-(插入点) - 1(插入点 = 元素应插入的位置,例如数组 [1,3,5] 查找 4,插入点为 2,返回 - 3);
- 考点 3:
sort对 null 的支持:引用类型数组允许 null(null 视为最小元素),基本类型数组无 null 概念。
2. 数组转集合
核心方法
java
String[] arr = {"a", "b", "c"};
// 数组转集合
List<String> list = Arrays.asList(arr);
面试考点
- 🔥 考点 1:返回值类型陷阱:
Arrays.asList()返回的是java.util.Arrays.ArrayList(Arrays 的私有静态内部类),不是java.util.ArrayList; - 🔥 考点 2:固定长度陷阱:该集合是固定长度 的,调用
add()/remove()会抛出UnsupportedOperationException; - 🔥 考点 3:强引用绑定陷阱:
- 数组与集合强绑定,修改数组元素会同步修改集合,反之亦然;
java
arr[0] = "x";
System.out.println(list.get(0)); // 输出 x(集合被同步修改)
- 🔥 考点 4:正确解决方案:
- 转成真正的
ArrayList以支持增删:
- 转成真正的
java
List<String> realList = new ArrayList<>(Arrays.asList(arr));
3. 数组复制 & 填充
核心方法
java
int[] arr = {1,2,3};
// 1. 全数组复制(扩容/缩容)
int[] copyArr1 = Arrays.copyOf(arr, 5); // 扩容到5,补0:[1,2,3,0,0]
int[] copyArr2 = Arrays.copyOf(arr, 2); // 缩容到2:[1,2]
// 2. 范围复制(左闭右开)
int[] rangeCopy = Arrays.copyOfRange(arr, 0, 2); // [1,2]
// 3. 全数组填充
Arrays.fill(arr, 0); // 全部填充0:[0,0,0]
// 4. 范围填充
Arrays.fill(arr, 0, 2, 1); // [0,2)填充1:[1,1,0]
面试考点
-
Arrays.copyOf()底层实现:调用System.arraycopy()(native 本地方法,基于内存块复制,比 for 循环高效); -
与
System.arraycopy()的区别:维度 Arrays.copyOf() System.arraycopy() 返回值 返回新数组 无返回值(修改目标数组) 灵活性 仅复制全数组 / 指定长度 可指定源 / 目标数组、起始位置 底层 调用 System.arraycopy () 原生本地方法 -
应用场景:
ArrayList底层扩容就是调用Arrays.copyOf()实现。
4. 数组比较 & 转字符串
核心方法
java
int[] arr1 = {1,2,3};
int[] arr2 = {1,2,3};
int[][] deepArr1 = {{1},{2}};
int[][] deepArr2 = {{1},{2}};
// 1. 一维数组比较(内容相等返回true)
boolean equals = Arrays.equals(arr1, arr2); // true
// 2. 多维数组比较
boolean deepEquals = Arrays.deepEquals(deepArr1, deepArr2); // true
// 3. 一维数组转字符串
String str = Arrays.toString(arr1); // "[1, 2, 3]"
// 4. 多维数组转字符串
String deepStr = Arrays.deepToString(deepArr1); // "[[1], [2]]"
面试考点
==与Arrays.equals()的区别:==:比较数组的引用地址(是否为同一个数组);Arrays.equals():比较数组的内容(每个元素是否相等);
- 多维数组必须用
deepEquals()/deepToString():一维方法无法穿透数组层级,多维数组用equals()会比较数组地址而非内容。
3、面试高频问题 & 易错陷阱
1. 高频问答
Arrays.asList() 为什么返回的集合不能增删?
- 答案:
Arrays.ArrayList未重写add()/remove()方法,直接继承自AbstractList,这些方法默认抛出UnsupportedOperationException。
Arrays.sort() 对基本类型和引用类型排序的算法不同,为什么?
- 答案:
- 基本类型无需考虑稳定性(无对象引用相等的问题),双轴快排效率更高;
- 引用类型需要排序稳定性(保证相等元素的相对位置不变),TimSort 更适配。
为什么 Arrays.binarySearch 要求数组升序排序?
- 答案:二分查找的核心逻辑是基于 "有序数组的中间值对比",无序数组会导致中间值判断失效,返回结果不可预测。
2. 易错陷阱(避坑指南)
- 用
Arrays.asList()处理基本类型数组,导致集合仅包含一个数组元素; - 认为
Arrays.asList()返回的是java.util.ArrayList,调用add()抛异常; - 多维数组用
equals()/toString()替代deepEquals()/deepToString(); - 未排序就调用
binarySearch,认为返回 - 1 表示未找到; - 混淆
Arrays.copyOf()和System.arraycopy()的使用场景。