Java 数组
Java 数组的长度、引用语义、多维形态与数组协变风险:固定长度容器如何承载同类型元素。
#type / concept
#status / growing
#tech / dev
#resource / java
[!info] 关联笔记
Java 数组
这个概念为什么出现
需要固定数量、可按下标随机访问的同构数据时,数组是语言内建方案。它比集合更“贴内存与 JVM 数组对象”,但也更原始:长度不可变、缺乏丰富 API,且存在数组协变带来的运行时 ArrayStoreException。
[!abstract] 一句话理解 数组是定长、可索引的对象;元素可以是基本类型或引用类型;
T[]在引用层面协变,因此运行时仍会检查存储类型。
最小可运行示例
场景:仓配系统按货位顺序装载 SKU 编号
分拣线有固定 4 个槽位,需要按下标写入/读取 SKU;若误把不兼容类型写入“商品数组”,应在运行时失败而不是静默污染。
public class SlotLoad {
// loadFixedSlots:把扫描到的 SKU 填进固定槽位。
//
// 业务意图:槽位数固定为 4,未扫到的保持 null。
// 教学点:
// - new String[n] 长度固定;
// - 引用数组元素默认 null;
// - 数组是对象,变量保存引用。
static String[] loadFixedSlots(String[] scanned) {
String[] slots = new String[4];
int n = Math.min(scanned.length, slots.length);
for (int i = 0; i < n; i++) {
slots[i] = scanned[i]; // 按下标写入
}
return slots;
}
// demoCovariance:展示 String[] 可当作 Object[] 传入,但非法存储会炸。
static void demoCovariance() {
String[] skus = {"A1", "B2"};
Object[] objs = skus; // 数组协变:编译允许
objs[0] = "C3"; // 仍是 String,OK
try {
objs[1] = Integer.valueOf(1); // 运行时 ArrayStoreException
} catch (ArrayStoreException ex) {
System.out.println("store-rejected: " + ex.getClass().getSimpleName());
}
System.out.println(skus[0] + "," + skus[1]);
}
public static void main(String[] args) {
String[] slots = loadFixedSlots(new String[]{"S1", "S2"});
System.out.println(slots.length + " " + slots[0] + " " + slots[2]); // 4 S1 null
demoCovariance();
}
}
建议运行:
javac SlotLoad.java && java SlotLoad
期望输出:
4 S1 null
store-rejected: ArrayStoreException
C3,B2
结合场景再看三个关注点
- 长度在创建时固定,扩容要自己
new+ 拷贝(或改用集合)。 - 引用数组可含 null,遍历前要想清楚空槽语义。
- 协变让“看起来像泛型”的数组更危险;泛型集合通常更安全。
核心概念与准确模型
1. 创建与长度
int[] a = new int[3]; // 0,0,0
String[] b = {"x","y"}; // 长度 2
int len = b.length; // 字段 length,不是方法
2. 基本类型数组 vs 引用数组
| 基本类型数组 | 引用数组 | |
|---|---|---|
| 元素默认 | 0/false 等 | null |
| 存 null | 否 | 可以 |
| 典型用途 | 计量、位图、性能敏感缓冲 | 对象列表快照 |
3. 多维数组
int[][] m = new int[2][3]:规则矩形int[][] jagged = new int[2][]:锯齿数组,每行可不同长
4. 拷贝
- 赋值
a=b只复制引用 System.arraycopy/Arrays.copyOf做元素级拷贝(浅拷贝对象引用)
5. 与集合边界
- 需要动态伸缩、丰富查询 →
List等 - 需要与变参、JNI、部分 API 交互 → 数组仍常见
边界情况与反直觉行为
length是字段;字符串才是length()。- 负长度创建 →
NegativeArraySizeException。 - 越界 →
ArrayIndexOutOfBoundsException。 - 泛型不能直接
new T[](擦除限制),常见用List或反射数组 API。
常见误区
[!warning] 常见误区:数组赋值是深拷贝 错误理解:
a=b后改a[i]不影响b。
正确模型:共享同一数组对象。
为何易错:与“值复制”语言/结构体思维混淆。
工程实践
- 对外 API 优先
List,边界层再转数组。 - 返回内部数组前先拷贝,避免暴露可变内部状态。
- 批量处理用
Arrays工具,但注意并行排序等的线程假设。 - 协变场景用泛型集合替代
Object[]乱传。
可验证实验
- 打印
new int[3]与new String[3]的元素默认值。 - 复现
ArrayStoreException。 - 比较
clone()后修改引用元素字段(浅拷贝)的可见性。
本节总结
- 数组定长、可索引、是对象
- 引用语义与协变是两大心智点
- 动态数据优先集合,数组留在性能与边界层
自测题
- 为什么
String[]可以赋给Object[]变量,但泛型List<String>不能直接当List<Object>? arr.length与str.length()差在哪?
参考答案
- 数组历史上协变且有运行时存储检查;泛型不变(invariant)且擦除后缺少同样的元素存储检查模型。
- 数组长度是字段;String 的长度是方法。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| JLS Array Types | 规范 | 数组类型与协变 |
| Arrays (Java SE) | API | 工具方法 |