首页 / Java 入门教程 / Arrays 工具类

Java 入门教程

Arrays 工具类

本教程共 100 篇 · 第 27 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

JavaJava 入门教程Arrays数组排序数组工具

27. Arrays 工具类

本节目标:掌握 java.util.Arrays 提供的排序、二分查找、填充、复制、比较、转字符串和转 List 等常用方法,让数组操作不再手写循环。

数组本身很朴素,只有一个 length 属性和下标访问,连打印内容都做不到。排序要自己写冒泡,复制要自己开循环,比较内容要逐个元素对——这些活每个项目都要重写一遍,纯属浪费。

java.util.Arrays 就是 JDK 为此准备的工具箱,里面全是操作数组的静态方法。用之前记得导包:

import java.util.Arrays;
```bash

它的方法都是 `Arrays.xxx(数组, ...)` 的形式,不需要 `new Arrays()`,实际上它的构造方法是私有的,压根 new 不出来。

## 打印数组:toString

直接打印数组变量,得到的是一串天书:

```java
int[] a = {3, 1, 4, 2};
System.out.println(a);   // [I@1b6d3586

[I 表示「int 数组」,@ 后面是哈希码,跟内容毫无关系。因为数组没有重写 toString(),用的还是 Object 的默认实现。

Arrays.toString 帮你把内容拼出来:

import java.util.Arrays;

public class ArraysDemo {
    public static void main(String[] args) {
        int[] a = {3, 1, 4, 2};
        System.out.println(Arrays.toString(a));   // [3, 1, 4, 2]
    }
}
```bash

> [!TIP]
> 调试时打印数组,永远用 `Arrays.toString`,别直接打印数组变量。二维及以上用 `Arrays.deepToString`,它会递归展开每一层。

## 排序:sort

`Arrays.sort` 把数组**原地**升序排列,不返回新数组:

```java
int[] a = {3, 1, 4, 2};
Arrays.sort(a);
System.out.println(Arrays.toString(a));   // [1, 2, 3, 4]

「原地」是关键。有人写 int[] b = Arrays.sort(a); 会编译报错,因为它的返回类型是 void。排完之后原数组 a 就已经有序了。

只排一段也行,区间是左闭右开

int[] b = {5, 4, 3, 2, 1};
Arrays.sort(b, 1, 4);      // 只排下标 1、2、3
System.out.println(Arrays.toString(b));   // [5, 2, 3, 4, 1]
```java

想要降序,基本类型数组没有现成参数,得先转成包装类型再传比较器:

```java
Integer[] c = {3, 1, 4, 2};
Arrays.sort(c, Comparator.reverseOrder());
System.out.println(Arrays.toString(c));   // [4, 3, 2, 1]

注意这里必须是 Integer[] 而不是 int[]。比较器只能作用于对象数组,基本类型没有对象可比。

内部实现上,基本类型用双轴快速排序,对象数组用 TimSort(归并的变体)。对象数组要求排序稳定——相等元素的原有先后顺序不能乱,快排做不到这点,所以两边用了不同算法。

二分查找:binarySearch

已排序的数组里查值,比逐个比对快得多:

int[] a = {1, 2, 3, 4};
int idx = Arrays.binarySearch(a, 3);
System.out.println(idx);   // 2,找到,返回下标
```java

没找到时返回的是一个负数,规则是 `-(插入点) - 1`:

```java
int[] a = {1, 2, 3, 4};
System.out.println(Arrays.binarySearch(a, 5));   // -5

5 应该插在下标 4 的位置,所以返回 -(4) - 1 = -5。这个设计的用意是:返回值 < 0 就代表没找到,同时还顺便告诉你该插在哪,一个返回值传递两条信息。

Warning

binarySearch 要求数组已经排好序。在无序数组上查,不会报错,但结果完全不可信——可能返回错的下标,也可能明明存在却说找不到。查之前先 sort

填充:fill 与 setAll

把整个数组或某一段填成同一个值:

int[] a = new int[5];
Arrays.fill(a, 7);             // 全部填 7
Arrays.fill(a, 1, 3, 9);       // 下标 [1,3) 填 9,即 a[1]、a[2]
System.out.println(Arrays.toString(a));   // [7, 9, 9, 7, 7]
```java

区间同样是左闭右开,`[1, 3)` 只改下标 12,不含 3。JDK 里凡是带 `from`、`to` 的方法几乎都遵守这个约定。

要按规律填值,用 Java 8 加入的 `setAll`,它接收一个「下标 → 值」的函数:

```java
int[] squares = new int[5];
Arrays.setAll(squares, i -> i * i);
System.out.println(Arrays.toString(squares));   // [0, 1, 4, 9, 16]

复制:copyOf 与 copyOfRange

数组长度不可变,要「扩容」只能复制到新数组。手写循环能做,但 copyOf 一行搞定:

int[] a = {1, 2, 3};
int[] bigger = Arrays.copyOf(a, 5);    // 变长到 5
System.out.println(Arrays.toString(bigger));   // [1, 2, 3, 0, 0]

int[] smaller = Arrays.copyOf(a, 2);   // 截短到 2
System.out.println(Arrays.toString(smaller));  // [1, 2]
```java

变长时多出来的位置填默认值,截短时多余部分直接丢弃。`ArrayList` 内部扩容用的就是这个方法。

只要中间一段,用 `copyOfRange`,依然左闭右开:

```java
int[] mid = Arrays.copyOfRange(a, 1, 3);   // 下标 1、2
System.out.println(Arrays.toString(mid));  // [2, 3]
Note

对象数组的复制是浅拷贝:新数组里存的还是原来那些对象的引用,改对象内容两边都变。要深拷贝得自己逐个克隆。

比较:equals 与 deepEquals

数组是对象,== 比的是「是不是同一个数组」,跟内容无关:

int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a == b);               // false,两个不同对象
System.out.println(a.equals(b));          // false,没重写,等价于 ==
System.out.println(Arrays.equals(a, b));  // true,逐元素比内容
```java

第二行尤其阴险。`a.equals(b)` 语法上完全合法、不报错,但数组没有重写 `equals`,继承的是 Object 版本,效果和 `==` 一模一样。想比内容必须显式用 `Arrays.equals`。

多维数组用 `Arrays.deepEquals`,`Arrays.equals` 只比一层引用,对二维数组永远返回 false

Java 9 还补了两个更好用的:

```java
int[] x = {1, 2, 3};
int[] y = {1, 2, 9};
System.out.println(Arrays.compare(x, y));   // 负数,字典序 x < y
System.out.println(Arrays.mismatch(x, y));  // 2,第一个不同的下标

compare 能直接给出大小关系,mismatch 告诉你第一处差异在哪,排查数据不一致时很省事。

转成 List:asList

想用集合的便利方法,可以把数组转成 List:

List<String> list = Arrays.asList("a", "b", "c");
System.out.println(list);   // [a, b, c]
```text

但这个 List 有两个必须知道的限制。

第一,它是**定长视图**,不是普通的 ArrayList。长度锁死,增删都会抛异常:

```java
list.add("d");      // UnsupportedOperationException
list.remove(0);     // UnsupportedOperationException
list.set(0, "z");   // 这个可以,改元素不改长度
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:155)
```java

第二,它是**视图**,和原数组共享同一块数据。改 List 会影响数组,改数组也会影响 List:

```java
String[] arr = {"a", "b"};
List<String> view = Arrays.asList(arr);
view.set(0, "z");
System.out.println(arr[0]);   // z,原数组跟着变了

要一个真正独立、可增删的 List,包一层:

List<String> real = new ArrayList<>(Arrays.asList(arr));
real.add("c");   // 正常
```bash

Java 9 之后,如果只是想要一个不可变列表,`List.of("a", "b", "c")` 更直接,语义也更清楚——它连 `set` 都不允许,是彻底的只读。

> [!WARNING]
> `Arrays.asList` 遇上基本类型数组会翻车。`Arrays.asList(new int[]{1,2,3})` 得到的是 `List<int[]>`,长度为 1,里面装着整个数组。因为泛型不接受基本类型,编译器只能把 `int[]` 整体当成一个元素。正确写法是 `Arrays.stream(arr).boxed().toList()`。

## 转成流:stream

`Arrays.stream` 把数组变成流,求和求最值一行搞定:

```java
int[] nums = {3, 1, 4, 2};
System.out.println(Arrays.stream(nums).sum());        // 10
System.out.println(Arrays.stream(nums).max().getAsInt());  // 4
System.out.println(Arrays.stream(nums).average().getAsDouble()); // 2.5

max() 返回的是 OptionalInt,因为空数组没有最大值,得用 getAsInt() 取出来。流的完整用法在后面章节展开,这里先知道有这条捷径。

常用方法速查

方法作用注意点
toString(a)转可读字符串多维用 deepToString
sort(a)原地升序排序返回 void,不产生新数组
sort(a, from, to)排某一段区间左闭右开
binarySearch(a, key)二分查找必须先排序;没找到返回 -(插入点)-1
fill(a, v)全部填同一个值也支持区间版
setAll(a, f)按下标规律填值Java 8+
copyOf(a, n)复制并调整长度变长补默认值,截短丢尾部
copyOfRange(a, from, to)复制一段区间左闭右开
equals(a, b)比较内容多维用 deepEquals
compare(a, b)字典序比大小Java 9+
mismatch(a, b)首个差异下标Java 9+,全同返回 -1
asList(...)转 List定长视图,不能增删
stream(a)转流配合 sum/max/average
hashCode(a)按内容算哈希多维用 deepHashCode

常见疑问

问:Arrays.sort 排 100 万个数会不会很慢?

不会。基本类型的双轴快排平均是 O(n log n),百万级数据在普通机器上通常几十毫秒量级。数据量再大且是多核机器,可以换 Arrays.parallelSort,它会拆成多段并行排。数据量小反而不要用并行版,线程调度的开销比排序本身还大。

问:Arrays 和后面要学的 Collections 是什么关系?

定位一样,服务对象不同。Arrays 操作数组,Collections 操作集合(List、Set 这些)。两者方法名有意起得很像,比如都有 sortfillbinarySearch,学会一个另一个基本能猜出来。

问:为什么 Arrays.toString 不做成数组自己的方法,写成 a.toString()

数组是 JVM 内建类型,不是普通的类,没法像写 String 那样给它加方法或重写 toString。这是 Java 早期的设计遗留。工具类是绕开这个限制的折中办法,用起来啰嗦一点,但功能不缺。