My App

数组在方法中传递

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 重新赋值数组引用

修改数组元素(会影响原数组)

在方法中修改数组元素,会影响原数组,因为 arrarray 指向同一个数组对象。

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 仍指向它
└─────────────────────┘

关键点总结

  1. 地址 0x0011 的含义

    • 0x0011 是堆内存中数组对象的地址
    • main 方法中的 arr 变量存储的是 0x0011
    • change 方法中的 arr 参数也存储的是 0x0011(引用的副本)
  2. 引用传递的体现

    • 两个变量(mainarrchangearr)都指向同一个数组对象
    • change 方法中修改数组元素,直接影响堆中的数组对象
    • main 方法中的 arr 看到的也是修改后的数组
  3. 栈帧的生命周期

    • 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;  // 实际上会影响原数组!
}

正确理解: 传递的是引用的副本,arrarray 指向同一个数组对象。

误区 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
    }
}

注意事项

  1. 传递的是引用的副本:数组参数传递的是引用的副本,不是数组本身
  2. 修改元素会影响原数组:在方法中修改数组元素,会影响原数组
  3. 重新赋值不会影响原数组:在方法中重新赋值数组引用,不会影响原数组
  4. 理解引用和对象的区别:引用是变量,对象是堆中的数据
  5. 避免混淆:区分"修改数组元素"和"重新赋值数组引用"

总结

  • 数组参数传递:传递的是数组引用的副本
  • 修改数组元素:会影响原数组,因为指向同一个对象
  • 重新赋值引用:不会影响原数组,只是改变了形参的指向
  • 返回数组:返回的是数组的引用
  • 核心原理:理解引用传递和对象共享的机制

On this page