数组在方法中传递
Java 数组作为方法参数传递的内存机制、引用传递原理、修改元素与重新赋值的区别
数组在方法中传递
理解数组在方法中传递的机制,对于掌握 Java 的引用传递和内存管理至关重要。
数组参数传递的基本原理
传递的是引用的副本
数组作为参数传递时,传递的是数组引用的副本,而不是数组本身。
public class ArrayParameterPassing {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
modifyArray(arr);
System.out.println("main 中的 arr[0]: " + arr[0]); // 输出: 100
}
public static void modifyArray(int[] array) {
array[0] = 100; // 修改数组元素
}
}内存分配示意图:
调用前:
栈(Stack) 堆(Heap)
┌──────────┬──────┐ ┌──────────┬──────┬──────┬──────┐
│ 变量名 │ 地址 │ │ 地址 │ [0] │ [1] │ [2] │
├──────────┼──────┤ ├──────────┼──────┼──────┼──────┤
│ arr │0x1000│ ──→ │ 0x1000 │ 1 │ 2 │ 3 │
└──────────┴──────┘ └──────────┴──────┴──────┴──────┘
调用 modifyArray(arr) 时:
栈(Stack) 堆(Heap)
┌──────────┬──────┐ ┌──────────┬──────┬──────┬──────┐
│ 变量名 │ 地址 │ │ 地址 │ [0] │ [1] │ [2] │
├──────────┼──────┤ ├──────────┼──────┼──────┼──────┤
│ arr │0x1000│ ──┐ │ 0x1000 │ 1 │ 2 │ 3 │
│ array │0x1000│ ──┘ │ │ │ │ │
└──────────┴──────┘ └──────────┴──────┴──────┴──────┘
↑ ↑
└────────────────────┘
arr 和 array 都指向同一个数组对象
修改 array[0] = 100 后:
栈(Stack) 堆(Heap)
┌──────────┬──────┐ ┌──────────┬──────┬──────┬──────┐
│ 变量名 │ 地址 │ │ 地址 │ [0] │ [1] │ [2] │
├──────────┼──────┤ ├──────────┼──────┼──────┼──────┤
│ arr │0x1000│ ──┐ │ 0x1000 │ 100 │ 2 │ 3 │
│ array │0x1000│ ──┘ │ │ │ │ │
└──────────┴──────┘ └──────────┴──────┴──────┴──────┘修改数组元素 vs 重新赋值数组引用
修改数组元素(会影响原数组)
在方法中修改数组元素,会影响原数组,因为 arr 和 array 指向同一个数组对象。
public class ModifyArrayElements {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
modifyElements(arr);
System.out.println("main 中的 arr[0]: " + arr[0]); // 输出: 100
System.out.println("main 中的 arr[1]: " + arr[1]); // 输出: 200
}
public static void modifyElements(int[] array) {
array[0] = 100; // 修改元素,会影响原数组
array[1] = 200; // 修改元素,会影响原数组
}
}说明: 修改数组元素会直接影响堆中的数组对象,因此原数组也会被修改。
重新赋值数组引用(不会影响原数组)
在方法中重新赋值数组引用,不会影响原数组,因为只是改变了形参 array 的指向。
public class ReassignArrayReference {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
reassignReference(arr);
System.out.println("main 中的 arr[0]: " + arr[0]); // 输出: 1(未改变)
}
public static void reassignReference(int[] array) {
array = new int[]{100, 200, 300}; // 重新赋值,不影响原数组
System.out.println("reassignReference 中的 array[0]: " + array[0]); // 输出: 100
}
}内存变化:
调用前:
栈: arr = 0x1000
堆: 0x1000 → [1, 2, 3]
调用 reassignReference(arr) 时:
栈: arr = 0x1000
array = 0x1000 (指向原数组)
堆: 0x1000 → [1, 2, 3]
方法中执行 array = new int[]{100, 200, 300}:
栈: arr = 0x1000 (不变)
array = 0x2000 (指向新数组)
堆: 0x1000 → [1, 2, 3] (原数组未改变)
0x2000 → [100, 200, 300] (新数组)
方法返回后:
栈: arr = 0x1000
array 被销毁
堆: 0x1000 → [1, 2, 3] (原数组未改变)
0x2000 → [100, 200, 300] (新数组,等待 GC 回收)完整示例对比
示例:修改元素 vs 重新赋值
public class ArrayPassingDemo {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = {10, 20, 30};
System.out.println("=== 修改数组元素 ===");
modifyElements(arr1);
System.out.println("main 中的 arr1[0]: " + arr1[0]); // 输出: 100
System.out.println("\n=== 重新赋值数组引用 ===");
reassignReference(arr2);
System.out.println("main 中的 arr2[0]: " + arr2[0]); // 输出: 10(未改变)
}
// 修改数组元素
public static void modifyElements(int[] array) {
array[0] = 100; // 会影响原数组
}
// 重新赋值数组引用
public static void reassignReference(int[] array) {
array = new int[]{100, 200, 300}; // 不会影响原数组
}
}详细案例:数组元素交换
案例:在方法中交换数组元素
这个案例展示了在方法中修改数组元素(交换操作)如何影响原数组。
public class Memory {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
// 计算交换前的数组元素和
int sumBefore = 0;
for (int i = 0; i < arr.length; i++) {
sumBefore += arr[i];
}
System.out.println("交换前数组元素和: " + sumBefore); // 输出: 6
// 调用 change 方法交换数组元素
change(arr);
// 计算交换后的数组元素和
int sumAfter = 0;
for (int i = 0; i < arr.length; i++) {
sumAfter += arr[i];
}
System.out.println("交换后数组元素和: " + sumAfter); // 输出: 6(和不变,但元素顺序改变)
// 验证交换结果
System.out.println("arr[0]: " + arr[0]); // 输出: 3
System.out.println("arr[2]: " + arr[2]); // 输出: 1
}
public static void change(int[] arr) {
// 交换第一个和最后一个元素
int temp = arr[0]; // temp = 1
arr[0] = arr[2]; // arr[0] = 3
arr[2] = temp; // arr[2] = 1
// 交换后数组变为: {3, 2, 1}
}
}执行结果:
交换前数组元素和: 6
交换后数组元素和: 6
arr[0]: 3
arr[2]: 1内存分配详细分析
问题: 0x0011 是哪一块空间?
答案: 0x0011 是堆内存中数组对象的地址。
完整内存分配示意图:
方法区(Method Area)
┌──────────────────────┐
│ Memory.class │
│ - main 方法字节码 │
│ - change 方法字节码 │
└──────────────────────┘
栈内存(Stack)
┌─────────────────────────────────────┐
│ change 方法的栈帧 │ ← 栈顶
│ - 参数: int[] arr = 0x0011 │
│ - 局部变量: int temp = 1 │
│ - 返回地址: main 方法 │
├─────────────────────────────────────┤
│ main 方法的栈帧 │
│ - 局部变量: int[] arr = 0x0011 │
│ - 局部变量: int sumBefore = 6 │
│ - 局部变量: int sumAfter = 6 │
└─────────────────────────────────────┘
│ │
│ │
└──────────┬─────────┘
│
↓
堆内存(Heap)
┌─────────────────────────────────────┐
│ 地址: 0x0011 │
│ ┌──────┬──────┬──────┐ │
│ │ [0] │ [1] │ [2] │ │
│ ├──────┼──────┼──────┤ │
│ │ 3 │ 2 │ 1 │ ← 交换后 │
│ └──────┴──────┴──────┘ │
│ (原值: [1, 2, 3]) │
└─────────────────────────────────────┘内存变化过程
1. 调用 change 方法前:
栈内存:
┌─────────────────────┐
│ main 方法栈帧 │
│ arr = 0x0011 │ ──┐
└─────────────────────┘ │
│
堆内存: │
┌─────────────────────┐ │
│ 0x0011 │ ←─┘
│ [1, 2, 3] │
└─────────────────────┘2. 调用 change(arr) 时:
栈内存:
┌─────────────────────┐
│ change 方法栈帧 │ ← 栈顶
│ arr = 0x0011 │ ──┐
│ temp = 1 │ │
├─────────────────────┤ │
│ main 方法栈帧 │ │
│ arr = 0x0011 │ ──┼─┐
└─────────────────────┘ │ │
│ │
堆内存: │ │
┌─────────────────────┐ │ │
│ 0x0011 │ ←─┘ │
│ [1, 2, 3] │ │
└─────────────────────┘ │
│
注意: main 的 arr 和 change 的 arr 都指向同一个数组对象3. 执行交换操作后:
栈内存:
┌─────────────────────┐
│ change 方法栈帧 │
│ arr = 0x0011 │ ──┐
│ temp = 1 │ │
├─────────────────────┤ │
│ main 方法栈帧 │ │
│ arr = 0x0011 │ ──┼─┐
└─────────────────────┘ │ │
│ │
堆内存: │ │
┌─────────────────────┐ │ │
│ 0x0011 │ ←─┘ │
│ [3, 2, 1] │ ←───┘ 数组元素已被修改
└─────────────────────┘4. change 方法返回后:
栈内存:
┌─────────────────────┐
│ main 方法栈帧 │ ← 栈顶(change 栈帧已销毁)
│ arr = 0x0011 │ ──┐
└─────────────────────┘ │
│
堆内存: │
┌─────────────────────┐ │
│ 0x0011 │ ←─┘
│ [3, 2, 1] │ 数组已被修改,main 中的 arr 仍指向它
└─────────────────────┘关键点总结
-
地址
0x0011的含义:0x0011是堆内存中数组对象的地址main方法中的arr变量存储的是0x0011change方法中的arr参数也存储的是0x0011(引用的副本)
-
引用传递的体现:
- 两个变量(
main的arr和change的arr)都指向同一个数组对象 - 在
change方法中修改数组元素,直接影响堆中的数组对象 main方法中的arr看到的也是修改后的数组
- 两个变量(
-
栈帧的生命周期:
change方法执行时,在栈中创建新的栈帧change方法返回后,其栈帧被销毁,但堆中的数组对象仍然存在main方法中的arr仍然指向修改后的数组
数组作为返回值
返回数组引用
方法可以返回数组,返回的是数组的引用。
public class ReturnArray {
public static void main(String[] args) {
int[] arr = createArray();
System.out.println("arr[0]: " + arr[0]); // 输出: 1
}
public static int[] createArray() {
int[] array = {1, 2, 3};
return array; // 返回数组引用
}
}内存分配:
createArray 方法中:
栈: array = 0x1000
堆: 0x1000 → [1, 2, 3]
方法返回后:
栈: array 被销毁
arr = 0x1000 (接收返回值)
堆: 0x1000 → [1, 2, 3] (数组仍然存在)多维数组的传递
二维数组作为参数
二维数组作为参数传递时,同样传递的是引用的副本。
public class TwoDimensionalArrayPassing {
public static void main(String[] args) {
int[][] arr = {{1, 2}, {3, 4}};
modify2DArray(arr);
System.out.println("main 中的 arr[0][0]: " + arr[0][0]); // 输出: 100
}
public static void modify2DArray(int[][] array) {
array[0][0] = 100; // 修改元素,会影响原数组
}
}内存分配:
栈: arr = 0x1000
堆: 0x1000 → [0x2000, 0x3000] (外层数组)
0x2000 → [1, 2] (内层数组)
0x3000 → [3, 4] (内层数组)
调用 modify2DArray(arr) 后:
栈: arr = 0x1000
array = 0x1000 (指向同一个二维数组)
堆: 0x1000 → [0x2000, 0x3000]
0x2000 → [100, 2] (元素被修改)
0x3000 → [3, 4]常见误区
误区 1:认为传递的是数组的副本
// 错误理解:认为传递的是数组的副本
public static void wrongUnderstanding(int[] array) {
// 错误:认为修改 array 不会影响原数组
array[0] = 100; // 实际上会影响原数组!
}正确理解: 传递的是引用的副本,arr 和 array 指向同一个数组对象。
误区 2:认为重新赋值会影响原数组
// 错误理解:认为重新赋值会影响原数组
public static void wrongUnderstanding2(int[] array) {
array = new int[]{100, 200, 300}; // 不会影响原数组
// 只是改变了形参 array 的指向
}正确理解: 重新赋值只是改变了形参的指向,不会影响实参。
实际应用场景
场景 1:在方法中修改数组元素
public class ArrayUtils {
// 将数组中的所有元素乘以 2
public static void multiplyByTwo(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = array[i] * 2; // 直接修改原数组
}
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
multiplyByTwo(arr);
// arr 现在是 [2, 4, 6, 8, 10]
}
}场景 2:在方法中创建新数组并返回
public class ArrayUtils {
// 返回数组的副本
public static int[] copyArray(int[] array) {
int[] newArray = new int[array.length];
for (int i = 0; i < array.length; i++) {
newArray[i] = array[i];
}
return newArray; // 返回新数组的引用
}
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = copyArray(arr1); // arr2 是 arr1 的副本
arr2[0] = 100; // 不会影响 arr1
}
}注意事项
- 传递的是引用的副本:数组参数传递的是引用的副本,不是数组本身
- 修改元素会影响原数组:在方法中修改数组元素,会影响原数组
- 重新赋值不会影响原数组:在方法中重新赋值数组引用,不会影响原数组
- 理解引用和对象的区别:引用是变量,对象是堆中的数据
- 避免混淆:区分"修改数组元素"和"重新赋值数组引用"
总结
- 数组参数传递:传递的是数组引用的副本
- 修改数组元素:会影响原数组,因为指向同一个对象
- 重新赋值引用:不会影响原数组,只是改变了形参的指向
- 返回数组:返回的是数组的引用
- 核心原理:理解引用传递和对象共享的机制