说明
数组是一种固定长度的容器,用于存储相同数据类型的多个数据值。
数组是一个引用数据类型,数组变量在栈内存中存储堆内存地址,数组元素在堆内存中存储实际的数据。
数组在存储数据的时候,需要结合隐式转化的问题。
- 举例:如果
int类型的数组,可以存储原始数据类型byte、short、int类型的数据,但是byte、short会自动拓宽为int类型的数据。 - 举例:如果
double类型的数组,可以存储原始数据类型byte、short、int、long、float、double类型的数据,但是byte、short、int、long、float会自动拓宽为double类型的数据。
📌隐式转化说明(复习)(或称拓宽转换)
隐式转换(类型自动提升):取值范围小的自动提升为取值范围为大的
- 由程序自动完成
- 取值范围小的和取值范围大的运算,先将取值范围小的的数据类型提升,后进行运算
byte、short、char类型在运算时,都会先直接提升为int,再进行运算。- 存储范围大小关系:
double>float>long>int>short>byte
- 建议:数组容器的类型,和存储的数据类型保持一致。
数组的定义和初始化
数组的定义
- 格式1(推荐):
<数据类型>[] <数组名>;。举例:int[] arr;。 - 格式2:
<数据类型> <数组名>[];。举例:int arr[];。

两种定义都没有报错,但是仍然建议使用第一种定义方式,即<数据类型>[] <数组名>;。
数组的定义方式:某大厂的编码规则
中括号是数组类型的一部分,数组定义如下:
String[] args;反例:使用
String args[]的方式来定义。
数组的初始化
初始化的说明:在内存中开辟存储空间,并将数据存储到数组容器的过程。
静态初始化
- 完整格式:
<数据类型>[] <数组名> = new <数据类型>[] {元素};,比如:int[] arr = new int[] {1, 2, 3}; - 简化格式:
<数据类型>[] <数组名> = {元素};,比如:int[] arr = {1, 2, 3};

数组一旦创建完毕后,数组的长度无法改变,即无法增加长度,也无法缩短长度。
使用静态初始化,JVM会根据传入数组的个数自动判断数组长度。
动态初始化
初始化时,只指定数组长度,由系统自动为数组分配初始值。
- 格式:
<数据类型>[] <数组名> = new <数据类型>[<数组长度>];,例如:int[] arr = new int[10];,即创建一个int类型的数组,长度为10。
关于系统自动为数组分配初始值的理解:
public static void main(String[] args) {
int[] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
}
0000000000
这里的初始值,并不是随机的数值,而是默认数值的,如下:
| 数据类型 | 数据类型关键字 | 默认值 |
|---|---|---|
| 整数类型 | byte、short、int、long |
0 |
| 小数类型 | float、double |
0.0 |
| 字符类型 | char |
'\u0000'(空字符) |
| 布尔类型 | boolean |
false |
| 引用数据类型 | null |
动态初始化和静态初始化的区别
- 动态初始化:手动指定数组长度,由系统给出初始化值。(适用于明确元素个数)
- 静态初始化:手动指定数组元素,系统根据元素个数计算出数组长度。(适用于明确元素的数据)
数组的地址值
我们编写如下代码
int arr[] = new int[] {1, 2, 3};
System.out.print(arr);

可以看到,输出的并不是数组内容,而是一串“乱码”,这串“乱码”,就是数组在内存中的地址值(数组在内存中的位置)。
扩展: 地址值的格式定义
我们以 [I@b4c966a 这个地址为例:
[:表示这是一个数组;-
I:表示这是一个int类型的数组;类型编码 数据类型 Iint Ddouble Ffloat Jlong Zboolean Cchar Bbyte Sshort L引用类型 @:是间隔符号,是固定格式;b4c966a:对象哈希码的十六进制表示,用于对象的唯一标识,不是真实的地址值。
数组和引用数据类型
数组是一个引用数据类型,引用数据类型在栈内存中只存储指向堆内存的地址值。
而在堆内存中,存储的是实际的数据值。
我们仍然以int[] arr = new int[1]为例:
栈内存:arr变量 -> 存储数组对象在堆内存中的地址
堆内存:数组对象包含:
- 数组元数据(长度、类型等)
- 连续的内存空间存储各个元素的值
通过arr[0]访问的是堆内存中数组对象的第一个元素存储的实际数据值。
数组的元素访问
- 格式:
<数组名>[<索引>];需要说明的是,数据的索引(索引亦称下标,角标)由0开始,连续不间断。
数组的遍历
- java中提供一个数组的长度属性,为<数组名>.length,如果希望遍历数组,则可以使这个length属性。
int[] arr = new int[] {1, 2, 3, 4, 5}; for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); }
数组的内存图
java的内存分配
java JVM虚拟机本地内存分为:
- 栈:方法运行时使用的内存。
- 堆:存储对象或者数组,所有
new来创建的,都存储在堆内存,会生成地址值。 - 方法区(元空间):存储可以运行的class文件
- 本地方法栈:JVM在使用操作系统功能的时候使用,与开发java程序无关。
- 寄存器:给CPU使用,与开发java程序无关。
说明:在JDK 7以前,堆和方法区是在一起的,在JDK 8+,将方法区拆封:新增元空间;原方法区部分功能移动到堆中,有些则移动到元空间中。
数组的内存
- 只要使用
new关键字创建的内容,是在堆内存中的; - 如果
new关键字使用了多次,则在堆中创建多个内存块。 -
当多个数组指向同一个堆内存时,对其中一个数组修改,则所有数组的相同索引全部会被修改。
arr2和arr1指向同一个堆内存块。修改arr1[1],同时也会修改arr2[1],如下是浅拷贝。public static void main(String[] args) { int arr1[] = new int[]{1, 2, 3, 4, 5}; int arr2[] = arr1; }
多维数组
多个低维数组可以组合为高维数组,比如多个一维数组可以组合成二维数组,多个二维数组可以组合成三维数组。
多维数组中,高维数组初始化的时候,允许低维数组长度不同。在申明多维数组变量的时候,需要申明最高维度数组的数量。
高维数组在栈内存中存的是高维数组在堆内存数据的引用,高维数组在堆内存中存储的是对低维数组的引用。

如下以二维数组为例。
静态初始化
- 格式:
<数据类型>[][] <数组名> = new <数据类型>[][] {{<元素1>, <元素2>}, {<元素1>, <元素2>}}; - 简化:
<数据类型>[][] <数组名> = {{<元素1>, <元素2>}, {<元素1>, <元素2>}}; - 举例:
int[][] arr2d = new int[][] {{1, 2, 3}, {2,3}};(高纬数组初始化的时候,允许低维数组长度不同)
动态初始化
<数据类型>[][] <数组名> =new <数据类型>[<最高维度数组的数量>][];- 举例:
int[][] arr2d = new int[2][];(需要申明最高维度数组的数量)
说明
- 动态初始化后需要单独初始化每个低维数组
- 访问未初始化的低维数组会导致
NullPointerException - 遍历时需使用
array[i].length适应不等长情况
数组的常见问题
索引越界
当访问了数组不存在的索引,就会引发索引越界。
示例代码:
int arr[] = {1, 2, 3, 4, 5};
System.out.println(arr[10]);
报错如下
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5
at Demo_20251031_1.main(Demo_20251031_1.java:4)
需要说明的是:java不存在-1这样的负数值索引
Java内存的说明
哈希码与内存地址的关系
- 在早期的JVM中,哈希码基于内存地址计算得出
- 在现代JVM中,哈希码不保证与内存地址相关
- 这是出于安全和性能的考虑
java与内存地址
- Java 被设计为一种内存安全的语言,不允许直接操作内存地址。这是 Java 的核心安全特性之一。
- 用于防止:缓冲区溢出攻击、野指针访问、内存泄漏(相对C/C++而言)、非法内存访问导致的程序崩溃。
- Java 程序运行在 JVM 上,真实内存地址对开发不可见,即使"获取"了某个地址,JVM可能会移动对象,导致地址失效。
内存回收
jvm会判断所有引用数据类型的指向,如果这个某个堆内存空间没有被引用的其他变量、对象引用,则会适时触发清理。
存在被引用
堆内存被str引用,不回收内存空间。
String str = new String("Hello");
不存在被引用
arr数组被指向null,引用解除,原arr对象无引用,适时被jvm回收
int[] arr = new int[1]{0};
arr = null;
空指针异常
- 数组是引用数据类型,数组变量在栈内存中存储的是指向堆内存中数组对象的地址引用。
- 当数组在栈内存中引用为null时(当数组引用被显式赋值为
null,或者未正确初始化时),访问元素会抛出空指针异常。
数组的深浅拷贝
深浅拷贝的定义
浅拷贝 (Shallow Copy)
创建一个新对象,但只复制原始对象中基本数据类型的值,对于引用类型的字段,只复制引用地址而不复制引用的对象本身。
特点:
- 新对象和原对象共享引用类型的数据
- 修改新对象中的引用类型数据会影响原对象
- 复制速度快,内存占用少
深拷贝 (Deep Copy)
创建一个新对象,并递归地复制原始对象及其所有引用对象,直到最基本的数据类型为止。
特点:
- 新对象和原对象完全独立,不共享任何数据
- 修改新对象不会影响原对象
- 复制速度慢,内存占用多
数组的深浅拷贝
浅拷贝:新数组变量(栈内存)指向原数组在堆内存中的同一数据对象,两个数组变量共享同一份堆内存数据。
int[] arrSource = new arrSource[]{1, 2, 3, 4, 5};
int[] arrNew = arrSource;
深拷贝:新数组变量(栈内存)指向堆内存中新建的独立数据对象,两个数组变量拥有各自独立的堆内存数据。
int[] arrSource = new arrSource[]{1, 2, 3, 4, 5};
int[] arrNew = new int[arrSource.length];
for (int i = 0; i < arrSource.length; i++) {
arrNew[i] = arrSource[i];
}
数组的深拷贝
使用**Object类的clone**方法
使用Object类的clone方法,以深拷贝数组。
public class Main {
public static void main(String[] args) {
int[] arrSource = new int[]{1, 2, 3, 4, 5};
int[] arrNew = arrSource.clone();
}
}
使用**java.util.Arrays类的copyOf**方法
需要引入java.util.Arrays类。
copyOf方法的语法为Arrays.copyOf(<原始数组>, <新数组的长度>)
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] arrSource = new int[]{1, 2, 3, 4, 5};
int[] arrNew = Arrays.copyOf(arrSource, arrSource.length);
}
}