Java 中的值传递作为参数传递机制

更新于 2025-12-27

baeldung 2024-01-08

1. 引言

向方法传递参数最常见的两种方式是“值传递(Pass-by-Value)”和“引用传递(Pass-by-Reference)”。不同的编程语言以不同的方式使用这些概念。就 Java 而言,所有参数传递都严格采用值传递机制

在本教程中,我们将通过示例说明 Java 在处理不同类型参数时是如何进行传递的。

2. 值传递 vs 引用传递

让我们先了解一些常见的参数传递机制:

  • 值传递(value)
  • 引用传递(reference)
  • 结果传递(result)
  • 值-结果传递(value-result)
  • 名称传递(name)

在现代编程语言中,最常用的两种机制是“值传递”和“引用传递”。在继续之前,我们先分别讨论这两种机制:

2.1 值传递(Pass-by-Value)

当参数采用值传递方式时,调用方(caller)和被调用方(callee)操作的是两个彼此独立的变量,它们互为副本。对其中一个变量的任何修改都不会影响另一个变量。

这意味着,在调用方法时,传递给被调用方法的参数是原始参数的克隆。被调用方法中对参数所做的任何修改,都不会影响调用方法中的原始参数。

2.2 引用传递(Pass-by-Reference)

当参数采用引用传递方式时,调用方和被调用方操作的是同一个对象。

这意味着,当变量以引用方式传递时,会将该对象的唯一标识符(即内存地址)发送给方法。对参数实例成员的任何修改,都会直接反映到原始对象上。

3. Java 中的参数传递机制

任何编程语言的基本概念都离不开“值”和“引用”。在 Java 中:

  • 基本类型(Primitive)变量直接存储实际的值;
  • **非基本类型(Non-Primitive,即对象)**变量存储的是引用,这些引用指向堆内存中对象的实际地址。

Java 中的所有参数都是按值传递的。在方法调用期间,每个参数(无论是基本类型的值还是对象的引用)都会在栈内存中创建一个副本,并将该副本传递给方法。

  • 对于基本类型:其值会被直接复制到栈内存中,然后传递给被调用方法;
  • 对于非基本类型(对象):栈中存储的是指向堆中对象的引用。当传递对象时,会复制该引用(即复制栈中的引用值),并将这个新的引用传递给方法。新旧引用都指向堆中的同一个对象

接下来,我们通过代码示例来具体说明。

3.1 传递基本类型

Java 语言定义了八种基本数据类型。基本类型变量的值直接存储在栈内存中。当以基本类型变量作为参数传递时,实际参数的值会被复制到形式参数中,这些形式参数在栈内存中拥有自己独立的空间。

这些形式参数的生命周期仅限于方法执行期间;方法返回后,这些参数就会从栈中清除并丢弃。

让我们通过以下代码示例来理解:

public class PrimitivesUnitTest {
 
    @Test
    public void whenModifyingPrimitives_thenOriginalValuesNotModified() {
        
        int x = 1;
        int y = 2;
       
        // 修改前
        assertEquals(x, 1);
        assertEquals(y, 2);
        
        modify(x, y);
        
        // 修改后
        assertEquals(x, 1);
        assertEquals(y, 2);
    }
    
    public static void modify(int x1, int y1) {
        x1 = 5;
        y1 = 10;
    }
}

让我们通过分析内存中的存储方式来理解上述断言:

  • 主方法中的变量 xy 是基本类型,其值直接存储在栈内存中;
  • 调用 modify() 方法时,会为这两个变量分别创建精确的副本,并存储在栈内存的不同位置;
  • 对这些副本的任何修改仅影响副本本身,不会改变原始变量。
graph subgraph C["modify() 方法调用后"] C1["x = 1"] C2["y = 2"] C3["x1 = 5"] C4["y1 = 10"] end subgraph B["modify() 方法调用时"] B1["x = 1"] B2["y = 2"] B3["x1 = 1"] B4["y1 = 2"] end subgraph A[初始栈空间] A1["x = 1"] A2["y = 2"] A3[" "] A4[" "] end

3.2 传递对象引用

在 Java 中,所有对象在底层都动态存储在堆(Heap)空间中,并通过引用变量进行访问。

与基本类型不同,Java 对象的存储分为两个部分:

  • 引用变量存储在栈内存中;
  • 实际对象存储在堆内存中。

当对象作为参数传递时,会创建该引用变量的一个精确副本,这个副本仍然指向堆中原来的同一个对象。

因此,如果在方法内部对对象的内容进行修改,这种修改会反映到原始对象上。但是,如果在方法内部将新的对象赋值给传入的引用变量,则不会影响原始对象

下面通过代码示例来说明:

public class NonPrimitivesUnitTest {
 
    @Test
    public void whenModifyingObjects_thenOriginalObjectChanged() {
        Foo a = new Foo(1);
        Foo b = new Foo(1);

        // 修改前
        assertEquals(a.num, 1);
        assertEquals(b.num, 1);
        
        modify(a, b);
        
        // 修改后
        assertEquals(a.num, 2);
        assertEquals(b.num, 1);
    }
 
    public static void modify(Foo a1, Foo b1) {
        a1.num++;  // 修改原对象的内容
        
        b1 = new Foo(1);  // b1 现在指向一个新对象
        b1.num++;
    }
}
 
class Foo {
    public int num;
   
    public Foo(int num) {
        this.num = num;
    }
}

我们来分析上述程序中的断言:

  • 初始时,对象 ab 都具有 num = 1,它们的引用分别指向堆中两个不同的对象;
graph LR subgraph "初始 Stack" ref_a[ref a] ref_b[ref b] end subgraph "初始 Heap" obj_a["Object a (a.num = 1)"] obj_b["Object b (b.num = 1)"] end ref_a --> obj_a ref_b --> obj_b
- 当调用 `modify(a, b)` 时,会创建 `a` 和 `b` 的引用副本 `a1` 和 `b1`,它们仍然指向原来的对象;
graph LR subgraph "modify()调用时的Stack" ref_a[ref a] ref_a1[ref a1] ref_b[ref b] ref_b1[ref b1] end subgraph "modify()调用时的Heap" obj_a["Object a (a.num = 1)"] obj_b["Object b (b.num = 1)"] end ref_a --> obj_a ref_a1 --> obj_a ref_b --> obj_b ref_b1 --> obj_b
- 在 `modify()` 方法中: - 对 `a1.num` 的修改(`a1.num++`)会直接影响原始对象 `a`,因此 `a.num` 变为 2; - 对 `b1` 重新赋值(`b1 = new Foo(1)`)使其指向一个新的堆对象,此后对 `b1` 的修改不再影响原始对象 `b`,因此 `b.num` 仍为 1。
graph LR subgraph "modify()调用后的Stack" ref_a[ref a] ref_a1[ref a1] ref_b[ref b] ref_b1[ref b1] end subgraph "modify()调用后的Heap" obj_a["Object a (a.num = 2)"] obj_b["Object b (b.num = 1)"] obj_b1["Object b1 (b1.num = 2)"] end ref_a --> obj_a ref_a1 --> obj_a ref_b --> obj_b ref_b1 --> obj_b1
## 4. 结论

在本文中,我们探讨了 Java 在处理基本类型和非基本类型(对象)参数时的传递机制。

我们了解到:Java 中的参数传递始终是值传递。但根据参数类型的不同,其表现形式有所差异:

  • 对于基本类型:传递的是值的副本;
  • 对于对象类型:传递的是对象引用的副本(即“引用的值”被复制),因此方法内可以修改原对象的内容,但不能改变原始引用所指向的对象本身。

简而言之:
Java 是值传递,但对于对象,传递的是引用的值(即地址的副本),而不是对象本身。