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

    • 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)

Arrays 工具类

白歌:"java.util.Arrays 是 JDK 提供给数组操作的核心工具类。我在 Code Review 中经常发现同事自己写排序、自己写打印方法,却不知道 Arrays 里已经内置了高效、经过充分测试的实现。这一节我们系统学习它的核心 API——重点不是记忆方法名,而是理解其适用场景和潜在陷阱。"


概述

java.util.Arrays 是 JDK 1.2 引入的数组工具类,位于 java.util 包下。它提供了一系列静态方法,涵盖了数组的排序、搜索、比较、填充、复制、转换等常见操作。

方法类别核心方法功能概述
打印toString() / deepToString()将数组转为可读字符串
排序sort() / parallelSort()对数组元素排序
查找binarySearch()二分查找指定元素
比较equals() / deepEquals()比较两个数组是否相等
填充fill()用指定值填充数组
复制copyOf() / copyOfRange()复制数组(支持扩容/截断)
转换asList()将数组转换为 List(有陷阱)

设计哲学:Arrays 的所有方法都是 public static(工具类模式),构造函数为 private,防止实例化。


Arrays.toString():数组转字符串

在 Arrays.toString() 出现之前,直接用 System.out.println(arr) 打印数组会得到一个看不懂的字符串(如 [I@15db9742——这是数组对象的 hashCode 十六进制表示)。

// ❌ 旧方法:直接打印数组
int[] arr = {1, 2, 3};
System.out.println(arr);  // 输出:[I@15db9742  —— 完全不可读

// ✅ 使用 Arrays.toString()
System.out.println(Arrays.toString(arr));  // 输出:[1, 2, 3]
方法适用维度示例输出
Arrays.toString(arr)一维数组[1, 2, 3]
Arrays.deepToString(arr)多维数组[[1, 2], [3, 4]]

注意:对多维数组使用 toString() 仍然会输出 [[I@xxx 这样的引用地址——必须用 deepToString()。


Arrays.sort():数组排序

Arrays.sort() 使用双轴快速排序(Dual-Pivot Quicksort),JDK 7 起由 Vladimir Yaroslavskiy 实现,平均时间复杂度 O(n log n)。

// 对基本类型数组排序(升序,自然顺序)
int[] scores = {85, 92, 78, 95, 88};
Arrays.sort(scores);
System.out.println(Arrays.toString(scores));  // [78, 85, 88, 92, 95]

// 对部分区间排序(fromIndex 包含,toIndex 不包含)
int[] scores2 = {85, 92, 78, 95, 88};
Arrays.sort(scores2, 1, 4);  // 只排序下标 1~3 的元素
System.out.println(Arrays.toString(scores2));  // [85, 78, 92, 95, 88]

自定义排序:Comparator

对于对象数组(如 String[]、自定义类数组),可使用 Comparator 自定义排序规则:

// 对字符串数组按长度排序
String[] names = {"大翔", "白歌", "小崔", "孔蓝"};
Arrays.sort(names, (a, b) -> a.length() - b.length());
System.out.println(Arrays.toString(names));  // [大翔, 白歌, 小崔, 孔蓝]

// 降序排序(使用 Comparator.reverseOrder())
Integer[] scores = {85, 92, 78, 95, 88};
Arrays.sort(scores, Comparator.reverseOrder());
System.out.println(Arrays.toString(scores));  // [95, 92, 88, 85, 78]

注意:Comparator.reverseOrder() 仅适用于对象类型(如 Integer[]),对基本类型 int[] 无效。降序排序 int[] 需手动反向遍历或先升序再反转。


Arrays.binarySearch():二分查找

前提条件:数组必须是已排序的(升序),否则结果不可预测!

int[] scores = {78, 85, 88, 92, 95};  // 必须已排序

int index = Arrays.binarySearch(scores, 88);
System.out.println("88分的下标:" + index);     // 2(找到了,返回下标)

int index2 = Arrays.binarySearch(scores, 80);
System.out.println("80分的下标:" + index2);    // -2(未找到,返回 -(插入点) - 1)

返回值规律:

情况返回值计算规则
找到元素>= 0元素在数组中的实际下标
未找到< 0-(插入点) - 1,其中插入点 = 第一个大于该值的元素下标
// 验证插入点公式
int[] arr = {10, 20, 30, 40, 50};
int result = Arrays.binarySearch(arr, 25);  // 25不在数组中
// 插入点 = 2(25应插入在下标2的位置,即20和30之间)
// 返回值 = -(2) - 1 = -3
System.out.println(result);  // -3

Arrays.copyOf() 与 Arrays.copyOfRange():数组复制

这两个方法是数组扩容/截断的标准手段,底层调用 System.arraycopy()——一个 native 方法,性能极高。

// copyOf:从头部开始复制指定长度
int[] original = {85, 92, 78, 95, 88};
int[] truncated = Arrays.copyOf(original, 3);    // 截断:只复制前3个
System.out.println(Arrays.toString(truncated));   // [85, 92, 78]

int[] expanded = Arrays.copyOf(original, 8);     // 扩容:超出部分填默认值0
System.out.println(Arrays.toString(expanded));    // [85, 92, 78, 95, 88, 0, 0, 0]

// copyOfRange:复制指定区间 [from, to)
int[] subArray = Arrays.copyOfRange(original, 1, 4);  // 下标1~3
System.out.println(Arrays.toString(subArray));         // [92, 78, 95]

底层原理:System.arraycopy() 是 native 方法,在 JVM 内部使用 memmove() 或类似的内存操作实现。对于基本类型数组,它是整块内存拷贝,速度远快于循环逐元素赋值。


Arrays.equals():数组相等比较

== 比较的是引用地址,Arrays.equals() 比较的是元素内容。

int[] arr1 = {85, 92, 78};
int[] arr2 = {85, 92, 78};
int[] arr3 = arr1;

System.out.println(arr1 == arr2);                      // false(不同对象)
System.out.println(arr1 == arr3);                      // true(同一引用)
System.out.println(Arrays.equals(arr1, arr2));         // true(内容相同)
System.out.println(Arrays.equals(arr1, arr3));         // true(内容相同)

// 多维数组比较:必须用 deepEquals()
int[][] matrix1 = {{1, 2}, {3, 4}};
int[][] matrix2 = {{1, 2}, {3, 4}};
System.out.println(Arrays.equals(matrix1, matrix2));       // false!(比较的是引用)
System.out.println(Arrays.deepEquals(matrix1, matrix2));   // true

Arrays.fill():数组填充

用指定值填充整个数组或指定区间。

// 填充整个数组
int[] scores = new int[5];
Arrays.fill(scores, 100);
System.out.println(Arrays.toString(scores));  // [100, 100, 100, 100, 100]

// 填充指定区间 [from, to)
int[] scores2 = {85, 92, 78, 95, 88};
Arrays.fill(scores2, 1, 4, 0);  // 下标1~3填充为0
System.out.println(Arrays.toString(scores2));  // [85, 0, 0, 0, 88]

Arrays.asList():数组转 List —— 重要陷阱!

这是 Arrays 中最容易被误用的方法,面试高频考点。

String[] names = {"大翔", "白歌", "小崔"};
List<String> list = Arrays.asList(names);
System.out.println(list);  // [大翔, 白歌, 小崔]

陷阱一:返回的 List 是固定大小的

Arrays.asList() 返回的是 Arrays 内部类 ArrayList(不是 java.util.ArrayList),它直接包装了原始数组,不支持 add / remove 操作。

// ❌ 错误:试图向 asList 返回的 List 添加元素
List<String> list = Arrays.asList("大翔", "白歌", "小崔");
list.add("孔蓝");  // UnsupportedOperationException!

// ❌ 错误:试图删除元素
list.remove(0);    // UnsupportedOperationException!

// ✅ 正确:如果需要可变 List,包装一层 new ArrayList
List<String> mutableList = new ArrayList<>(Arrays.asList("大翔", "白歌", "小崔"));
mutableList.add("孔蓝");  // 正常

陷阱二:基本类型数组的 asList 行为

这是最隐蔽的陷阱——对基本类型数组调用 asList(),整个数组被当作单个元素!

// ❌ 陷阱:int[] 被当作一个对象
int[] scores = {85, 92, 78};
List<int[]> list = Arrays.asList(scores);  // List 中只有一个元素:整个 int[] 数组!
System.out.println(list.size());           // 1!不是 3!
System.out.println(list.get(0));           // [I@15db9742(数组的地址)

// ✅ 正确:使用包装类 Integer[]
Integer[] scores2 = {85, 92, 78};
List<Integer> list2 = Arrays.asList(scores2);  // List 中有三个元素
System.out.println(list2.size());             // 3
System.out.println(list2);                     // [85, 92, 78]

原因分析:Arrays.asList(T... a) 是泛型可变参数方法。int[] 不是引用类型,无法作为泛型类型参数——Java 将整个 int[] 推断为 T 的一个具体实例(T = int[]),因此 asList 创建的是包含单个数组对象的 List。


完整代码示例

示例一:飞翔科技员工考核排名系统

场景:大翔要求小崔实现一个员工季度考核排名系统,包括成绩录入、排序、查找特定员工排名、以及 Top N 展示。

import java.util.Arrays;

/**
 * 飞翔科技 —— 员工季度考核排名系统
 * 演示 sort / binarySearch / copyOf / toString 综合使用
 */
public class EmployeeRanking {
    public static void main(String[] args) {
        // 1. 员工姓名与考核分数(对应下标)
        String[] names = {"大翔", "白歌", "小崔", "孔蓝", "李明", "张伟", "王芳"};
        int[] scores = {91, 95, 78, 88, 82, 76, 93};

        System.out.println("========== 飞翔科技 Q2 季度考核 ==========\n");
        System.out.println("原始数据:");
        for (int i = 0; i < names.length; i++) {
            System.out.printf("  %s:%d 分\n", names[i], scores[i]);
        }

        // 2. 复制一份原数组用于排序(保留原始顺序)
        int[] sortedScores = Arrays.copyOf(scores, scores.length);
        Arrays.sort(sortedScores);  // 升序排列

        System.out.println("\n分数排名(升序):");
        System.out.println(Arrays.toString(sortedScores));

        // 3. 展示 Top 3(降序)
        System.out.println("\n🏆 Top 3 优秀员工:");
        int count = 0;
        for (int i = sortedScores.length - 1; i >= 0 && count < 3; i--) {
            // 找到该分数对应的员工姓名
            for (int j = 0; j < scores.length; j++) {
                if (scores[j] == sortedScores[i]) {
                    System.out.printf("  第%d名:%s(%d分)\n", count + 1, names[j], sortedScores[i]);
                    break;
                }
            }
            count++;
        }

        // 4. 查找孔蓝的排名
        int konglanScore = scores[3];  // 孔蓝的原始分数
        Arrays.sort(scores);           // 对原数组排序(注意:这会影响scores数组!)
        int rankIndex = Arrays.binarySearch(sortedScores, konglanScore);
        // 升序数组中,排名 = 总人数 - 下标
        int rank = sortedScores.length - rankIndex;
        System.out.printf("\n孔蓝成绩:%d 分,排名第 %d\n", konglanScore, rank);

        // 5. 计算分数段统计
        int excellent = 0;  // >= 90
        int good = 0;       // 80-89
        int normal = 0;     // 70-79
        for (int s : scores) {
            if (s >= 90) excellent++;
            else if (s >= 80) good++;
            else normal++;
        }
        System.out.println("\n分数段统计:");
        System.out.printf("  优秀(>=90):%d 人\n", excellent);
        System.out.printf("  良好(80-89):%d 人\n", good);
        System.out.printf("  一般(70-79):%d 人\n", normal);
    }
}
运行输出:
========== 飞翔科技 Q2 季度考核 ==========

原始数据:
  大翔:91 分
  白歌:95 分
  小崔:78 分
  孔蓝:88 分
  李明:82 分
  张伟:76 分
  王芳:93 分

分数排名(升序):
[76, 78, 82, 88, 91, 93, 95]

🏆 Top 3 优秀员工:
  第1名:白歌(95分)
  第2名:王芳(93分)
  第3名:大翔(91分)

孔蓝成绩:88 分,排名第 4

分数段统计:
  优秀(>=90):3 人
  良好(80-89):2 人
  一般(70-79):2 人

示例二:飞翔科技商品价格批量调整

场景:孔蓝负责测试飞翔科技电商平台的商品管理模块。她需要验证"批量调价"功能的正确性——对一组商品价格进行统一调整后,对比新旧价格数组是否一致、是否有遗漏。

import java.util.Arrays;

/**
 * 飞翔科技 —— 商品价格批量调整验证
 * 演示 equals / fill / copyOf / asList 的使用与陷阱规避
 */
public class ProductPriceAdjuster {
    public static void main(String[] args) {
        System.out.println("========== 飞翔科技电商平台 —— 商品调价验证 ==========\n");

        // 1. 定义商品价格(使用包装类 Integer[],避免 asList 陷阱)
        Integer[] oldPrices = {199, 299, 159, 399, 259};
        String[] productNames = {"机械键盘", "蓝牙耳机", "无线鼠标", "显示器支架", "USB集线器"};

        System.out.println("商品原价:");
        System.out.println(Arrays.toString(oldPrices));

        // 2. 使用 asList 展示价格列表(Integer[] 安全使用)
        System.out.println("\n价格列表:" + Arrays.asList(oldPrices));
        System.out.println("列表元素个数 = " + Arrays.asList(oldPrices).size() + "(正确为5)");

        // 3. 复制并统一上调 10%
        Integer[] newPrices = Arrays.copyOf(oldPrices, oldPrices.length);
        for (int i = 0; i < newPrices.length; i++) {
            newPrices[i] = (int) (newPrices[i] * 1.1);
        }
        System.out.println("\n调价后(上调10%):");
        for (int i = 0; i < productNames.length; i++) {
            System.out.printf("  %s:¥%d → ¥%d\n", productNames[i], oldPrices[i], newPrices[i]);
        }

        // 4. 验证调价是否正确
        Integer[] expectedPrices = {219, 329, 175, 439, 285};  // 手动计算的期望值
        // 注意:199*1.1=218.9→219,但Java int截断为218!
        // 这是浮点精度陷阱,此处仅作演示
        System.out.println("\n期望价格:" + Arrays.asList(expectedPrices));

        boolean allCorrect = Arrays.equals(newPrices, expectedPrices);
        System.out.println("调价是否正确?" + (allCorrect ? "是" : "否(浮点截断导致偏差)"));

        // 5. 使用 fill 重置测试数据
        Integer[] testPrices = new Integer[5];
        Arrays.fill(testPrices, 0);
        System.out.println("\n测试环境重置:验证所有价格为0 → " + Arrays.equals(testPrices, new Integer[]{0, 0, 0, 0, 0}));

        // 6. 演示 asList 陷阱(仅演示,不运行破坏性操作)
        System.out.println("\n========== Arrays.asList 陷阱演示 ==========");

        // 陷阱1: int[] → List<int[]>
        int[] primitivePrices = {199, 299, 159};
        System.out.println("int[] 转 List 的元素个数:" + Arrays.asList(primitivePrices).size() + "(应该为3,实际为1!)");

        // 陷阱2: asList 返回的 List 不可变
        System.out.print("尝试在 asList 返回的 List 上执行 add():");
        try {
            Arrays.asList(oldPrices).add(999);
        } catch (UnsupportedOperationException e) {
            System.out.println("抛出 UnsupportedOperationException(符合预期)");
        }

        System.out.println("\n========== 正确用法总结 ==========");
        System.out.println("1. 基本类型数组转 List 用 Integer[]/Long[] 等包装类");
        System.out.println("2. 需要可变 List 时用 new ArrayList<>(Arrays.asList(...))");
        System.out.println("3. 多维度数组比较用 deepEquals / deepToString");
    }
}
运行输出:
========== 飞翔科技电商平台 —— 商品调价验证 ==========

商品原价:
[199, 299, 159, 399, 259]

价格列表:[199, 299, 159, 399, 259]
列表元素个数 = 5(正确为5)

调价后(上调10%):
  机械键盘:¥199 → ¥218
  蓝牙耳机:¥299 → ¥328
  无线鼠标:¥159 → ¥174
  显示器支架:¥399 → ¥438
  USB集线器:¥259 → ¥284

期望价格:[219, 329, 175, 439, 285]
调价是否正确?否(浮点截断导致偏差)

测试环境重置:验证所有价格为0 → true

========== Arrays.asList 陷阱演示 ==========
int[] 转 List 的元素个数:1(应该为3,实际为1!)
尝试在 asList 返回的 List 上执行 add():抛出 UnsupportedOperationException(符合预期)

========== 正确用法总结 ==========
1. 基本类型数组转 List 用 Integer[]/Long[] 等包装类
2. 需要可变 List 时用 new ArrayList<>(Arrays.asList(...))
3. 多维度数组比较用 deepEquals / deepToString

易错场景

反例一:未排序就调用 binarySearch

// ❌ 错误:数组未排序就使用 binarySearch
int[] scores = {85, 92, 78, 95, 88};
int index = Arrays.binarySearch(scores, 92);  // 结果不可预测!

// ✅ 正确:先排序再查找
int[] scores = {85, 92, 78, 95, 88};
Arrays.sort(scores);                          // [78, 85, 88, 92, 95]
int index = Arrays.binarySearch(scores, 92);  // 3(正确)

反例二:对多维数组使用 toString 而非 deepToString

// ❌ 错误:二维数组用 toString
int[][] matrix = {{1, 2}, {3, 4}};
System.out.println(Arrays.toString(matrix));
// 输出:[[I@15db9742, [I@6d06d69c]  —— 不可读!

// ✅ 正确:用 deepToString
System.out.println(Arrays.deepToString(matrix));
// 输出:[[1, 2], [3, 4]]

反例三:将 asList 返回的 List 当作可变 List

// ❌ 错误:Arrays.asList 返回的 List 不支持结构性修改
List<String> list = Arrays.asList("大翔", "白歌", "小崔");
list.add("孔蓝");   // UnsupportedOperationException
list.remove(0);     // UnsupportedOperationException
list.clear();       // UnsupportedOperationException

// ✅ 正确:包装为真正的 ArrayList
List<String> list = new ArrayList<>(Arrays.asList("大翔", "白歌", "小崔"));
list.add("孔蓝");   // 正常

反例四:用 equals 比较多维数组

// ❌ 错误:二维数组用 equals
int[][] m1 = {{1, 2}, {3, 4}};
int[][] m2 = {{1, 2}, {3, 4}};
System.out.println(Arrays.equals(m1, m2));  // false!(比较的是行引用)

// ✅ 正确:用 deepEquals
System.out.println(Arrays.deepEquals(m1, m2));  // true

面试考点

Q1:Arrays.sort() 对 int[] 和 Integer[] 分别使用什么排序算法?为什么不同?

对 int[](基本类型):使用双轴快速排序(Dual-Pivot Quicksort),平均 O(n log n),最坏 O(n²)(但通过随机化等策略,实际很少触发最坏情况)。选择快速排序是因为基本类型对稳定性没有要求,而快速排序的常数因子更小,实际性能更优。

对 Integer[](对象类型):使用TimSort(一种改进的归并排序),保证 O(n log n) 且是稳定的。因为对象排序通常依赖 Comparator,用户可能期望稳定排序(相等元素的相对顺序不变)。TimSort 在部分有序的数据上性能尤其出色,接近 O(n)。

Q2:Arrays.binarySearch() 在未排序数组上调用会发生什么?

结果是未定义的(undefined)。可能返回一个错误的负数、可能偶然返回正确的下标,也可能返回一个完全无关的数。Javadoc 明确说明:如果数组未排序,结果是不可预测的。这是典型的"垃圾进,垃圾出"——方法不会检查数组是否有序,调用者负责保证前置条件。

Q3:Arrays.asList() 返回的 List 和 new ArrayList<>() 有什么本质区别?

① Arrays.asList() 返回的是 Arrays 的内部类 ArrayList,它只是原始数组的一个视图(view),没有复制数据。对该 List 的 set() 操作会直接修改底层数组。 ② 它不支持结构性修改(add、remove、clear),调用会抛出 UnsupportedOperationException。 ③ java.util.ArrayList 是真正的独立集合,数据独立存储,支持所有操作。

关键公式:new ArrayList<>(Arrays.asList(arr)) = 可变的、独立存储的 ArrayList。

Q4:Arrays.copyOf() 和 System.arraycopy() 有什么区别?

Arrays.copyOf() 是高层封装,内部调用 System.arraycopy()。区别在于:

  • copyOf() 返回一个新数组,自动处理创建目标数组的逻辑,支持扩容/截断
  • arraycopy() 需要调用者预先创建目标数组,不会自动创建
  • copyOf() 更安全易用,arraycopy() 更灵活(如复制到已有数组的指定位置)

Q5:为什么 int[] 不能直接用于 Arrays.asList()?如何正确转换?

Arrays.asList(T... a) 的参数类型是泛型可变参数,泛型不支持基本类型。int[] 被当作一个单独的引用类型(T = int[]),因此整个数组成为一个 List 元素。

正确转换方式: ① 使用包装类 Integer[] 替代 int[](推荐) ② 如果已经是 int[],用循环或 Stream API 手动转换:

List<Integer> list = Arrays.stream(intArray).boxed().collect(Collectors.toList());

白歌的结语:"掌握 Arrays 工具类,是区分初级开发和有经验开发者的重要标志。不要重复造轮子——JDK 提供的这些方法经过了数十年的测试和优化,比你手写的版本更可靠、更高效。但请务必记住那几个陷阱:binarySearch 需要排序、asList 不可修改且不兼容基本类型、多维数组要用 deep 系列方法。这些知识点在面试中出现的频率极高。"

上一页
多维数组