元数据卡
- 前置知识:第6章前文(数组声明、索引与遍历)
- 预计时间:15 分钟
- 阅读模式:👣稳步推进
- 完成标志:会复制数组、用 Arrays 工具类排序和搜索、理解数组固定长度特性
问题一:街区不能扩建
你刚建好一条 3 户人的街道,突然来了第 4 个人想入住。
int[] scores = new int[3];
scores[0] = 85;
scores[1] = 92;
scores[2] = 78;
// 想放第四个人?
scores[3] = 95; // ⚠️ ArrayIndexOutOfBoundsException!语言:Java 为什么会报错:数组的长度一旦创建就不可变。合法索引 0~2,scores[3] 越界了。
你需要更多空间?只能建一条更大的新街,然后把原来的住户搬过去:
int[] oldScores = {85, 92, 78};
int[] newScores = new int[5]; // 更大的街道
// 手动搬过去
for (int i = 0; i < oldScores.length; i++) {
newScores[i] = oldScores[i];
}
// 新住户入住
newScores[3] = 95;
newScores[4] = 88;这就是 ArrayList 的原理:ArrayList 内部维护一个数组,当空间不够时就自动"扩建"——实际上就是建个更大的新数组,旧数据搬过去。后面第 10 章会详细讲。
问题二:怎么复制一条一模一样的街?
你有三条路可以复制数组。
方法一:手动循环(最直观)
int[] source = {1, 2, 3, 4, 5};
int[] dest = new int[source.length];
for (int i = 0; i < source.length; i++) {
dest[i] = source[i];
}方法二:System.arraycopy()(原生方法,最快)
int[] source = {1, 2, 3, 4, 5};
int[] dest = new int[5];
// System.arraycopy(源数组, 源起始位置, 目标数组, 目标起始位置, 要复制的数量)
System.arraycopy(source, 0, dest, 0, source.length);
// 也可以只复制一部分
System.arraycopy(source, 1, dest, 2, 3);
// dest 变成 [0, 0, 2, 3, 4]语言:Java 关键点:System.arraycopy 是 native 方法——直接在内存层面复制,速度最快
方法三:Arrays.copyOf()(Java 6+,最常用)
import java.util.Arrays;
int[] source = {1, 2, 3, 4, 5};
// 复制整个数组
int[] copy = Arrays.copyOf(source, source.length);
// 也可以指定新长度——自动截断或补零
int[] longer = Arrays.copyOf(source, 10); // [1,2,3,4,5,0,0,0,0,0]
int[] shorter = Arrays.copyOf(source, 3); // [1,2,3]——截断了!语言:Java,需要 import java.util.Arrays;何时用:日常开发最常用的复制方式。它内部调用了 System.arraycopy
浅拷贝 vs 深拷贝——第一次见
刚才的复制方法对基本类型数组(int[]、double[] 等)没问题,因为每个格子只存数值。
但对引用类型数组(比如 String[] 或 Person[]),复制的是引用——两个数组指向同一个对象。修改其中一个元素对象,另一个也会看到。这叫浅拷贝。
如果每个元素对象本身也要复制一份,那叫深拷贝。深拷贝的完整实现留到 OOP 和泛型后复访,现在先记住这两个名字。
问题三:排序、搜索、填充——每次都自己写循环?
当然不。java.util.Arrays 工具类帮你搞定一切。
import java.util.Arrays;
public class ArraysDemo {
public static void main(String[] args) {
int[] numbers = {42, 7, 15, 8, 23, 3};
// 1. 排序
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
// [3, 7, 8, 15, 23, 42]
// 2. 二分搜索(必须先排序!)
int index = Arrays.binarySearch(numbers, 15);
System.out.println("15 的位置:" + index); // 3
// 3. 如果找不到,返回负数(表示插入点)
int notFound = Arrays.binarySearch(numbers, 99);
System.out.println("99 找不到:" + notFound); // 负数
// 4. 填充
int[] filled = new int[5];
Arrays.fill(filled, 42);
System.out.println(Arrays.toString(filled));
// [42, 42, 42, 42, 42]
// 5. 比较相等
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
int[] c = {3, 2, 1};
System.out.println(Arrays.equals(a, b)); // true
System.out.println(Arrays.equals(a, c)); // false(顺序不同)
// 6. Arrays.toString —— 打印数组
System.out.println(Arrays.toString(new int[]{10, 20, 30}));
// [10, 20, 30]
}
}语言:Java,需要 import java.util.Arrays重点方法速查:
| 方法 | 作用 | 注意 |
|---|---|---|
sort(a) | 排序(升序) | 内部用 Dual-Pivot Quicksort,很快 |
binarySearch(a, key) | 二分查找 | 数组必须先排序 |
fill(a, val) | 全填充为某值 | 也可指定范围 fill(a, from, to, val) |
equals(a, b) | 比较两个数组是否完全相同 | 长度 + 每个元素都得一样 |
copyOf(a, newLen) | 复制数组 | 自动截断或补默认值 |
toString(a) | 把数组转成可读字符串 | 不然打印出来是 [I@1b6d3586——内存地址 |
切记:
System.out.println(numbers)打印的是[I@15db9742——内存地址,毫无用处。一定要用Arrays.toString(numbers)。
差异窗口:C++ 和 Python 的排序
C++:
#include <algorithm>
int numbers[] = {42, 7, 15, 8, 23, 3};
std::sort(std::begin(numbers), std::end(numbers));C++ 的 std::sort 需要传入起始和结束迭代器(指针),不像 Java 那样直接用数组名。
Python:
numbers = [42, 7, 15, 8, 23, 3]
numbers.sort() # 原地排序,不返回新数组
sorted_numbers = sorted(numbers) # 返回新列表,原列表不变Python 的 sorted() 返回新排序列表——这在函数式编程中更常见,不修改原始数据。
(附录)可变参数——收任意多个同类型的东西
你写了一个方法要计算一堆数字的平均值。调用者可能传 2 个,也可能传 10 个。
不用可变参数的话,你只能写一堆重载方法——或者让调用者手动把数字装成数组。Java 5 引入了可变参数(varargs):
public class VarargsDemo {
public static void main(String[] args) {
System.out.println(average(85, 92, 78)); // 3 个数
System.out.println(average(90, 95)); // 2 个数
System.out.println(average(88, 76, 93, 85, 91)); // 5 个数
}
// 可变参数用三个点表示
static double average(int... numbers) {
int sum = 0;
for (int n : numbers) {
sum += n;
}
return (double) sum / numbers.length;
}
}语言:Java 预期输出:
85.0
92.5
86.6语法:int... numbers —— 本质上等价于 int[] numbers,但调用时可以传多个参数而不用手动创建数组 规则:
- 每个方法最多只能有一个可变参数
- 可变参数必须是最后一个参数
- 可以传任意数量(包括 0 个)
// 正确的声明
void method(String name, int... scores) { } // ✅ 可变参数在最后
// 支持的调用方式
method("张三"); // 传 0 个可变参数——numbers 长度为 0
method("张三", 85); // 传 1 个
method("张三", 85, 92, 78); // 传多个
method("张三", new int[]{85, 92}); // 也可以直接传数组本质:Java 在编译时把可变参数转换成了数组。你在方法体内看到
int... numbers,它的类型就是int[]。
🧠 大脑缓存
- 数组长度建了就定了,想要更大只能新建 + 复制
- 复制三式:手写循环(慢)、
System.arraycopy(快)、Arrays.copyOf(日常用) - 基本类型数组复制没坑;引用类型数组是浅拷贝
Arrays.sort、binarySearch、fill、equals、toString——别自己造轮子int...可变参数本质就是数组
下一步
一条街不够用了?有时候你需要的是棋盘、矩阵、图像——二维甚至多维的数组。下一节我们看看街区的街区长什么样。